blob: b7bfaca0f3eb703aa60ab5ca9fd58f5dc60705ab [file] [log] [blame]
// 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 "components/feed/core/feed_scheduler_host.h"
#include <algorithm>
#include <string>
#include <vector>
#include "base/bind.h"
#include "base/memory/weak_ptr.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/simple_test_clock.h"
#include "components/feed/core/pref_names.h"
#include "components/feed/core/refresh_throttler.h"
#include "components/feed/core/time_serialization.h"
#include "components/feed/core/user_classifier.h"
#include "components/feed/feed_feature_list.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/testing_pref_service.h"
#include "components/variations/variations_params_manager.h"
#include "components/web_resource/web_resource_pref_names.h"
#include "net/base/network_change_notifier.h"
#include "testing/gtest/include/gtest/gtest.h"
using base::Time;
using base::TimeDelta;
namespace feed {
namespace {
// Fixed "now" to make tests more deterministic.
char kNowString[] = "2018-06-11 15:41";
} // namespace
class ForceDeviceOffline : public net::NetworkChangeNotifier {
public:
ConnectionType GetCurrentConnectionType() const override {
return NetworkChangeNotifier::CONNECTION_NONE;
}
};
class FeedSchedulerHostTest : public ::testing::Test {
public:
void FixedTimerCompletion() { fixed_timer_completion_count_++; }
protected:
FeedSchedulerHostTest() : weak_factory_(this) {
FeedSchedulerHost::RegisterProfilePrefs(profile_prefs_.registry());
RefreshThrottler::RegisterProfilePrefs(profile_prefs_.registry());
UserClassifier::RegisterProfilePrefs(profile_prefs_.registry());
local_state()->registry()->RegisterBooleanPref(::prefs::kEulaAccepted,
true);
profile_prefs()->registry()->RegisterBooleanPref(
prefs::kArticlesListVisible, true);
Time now;
EXPECT_TRUE(Time::FromUTCString(kNowString, &now));
test_clock_.SetNow(now);
NewScheduler();
}
void InitializeScheduler(FeedSchedulerHost* scheduler) {
scheduler->Initialize(
base::BindRepeating(&FeedSchedulerHostTest::TriggerRefresh,
base::Unretained(this)),
base::BindRepeating(&FeedSchedulerHostTest::ScheduleWakeUp,
base::Unretained(this)),
base::BindRepeating(&FeedSchedulerHostTest::CancelWakeUp,
base::Unretained(this)));
}
// Recreates a new copy of the scheduler. This is useful if a test case needs
// to change some global state like prefs or params before the scheduler's
// constructor runs.
void NewScheduler() {
scheduler_ = std::make_unique<FeedSchedulerHost>(
&profile_prefs_, &local_state_, &test_clock_);
InitializeScheduler(scheduler());
}
// Note: Time will be advanced.
void ClassifyAsRareNtpUser() {
// By moving time forward from initial seed events, the user will be moved
// into kRareNtpUser classification.
test_clock()->Advance(TimeDelta::FromDays(7));
}
// Note: Time will be advanced.
void ClassifyAsActiveSuggestionsConsumer() {
// Click on some articles to move the user into kActiveSuggestionsConsumer
// classification. Separate by at least 30 minutes for different sessions.
scheduler()->OnSuggestionConsumed();
test_clock()->Advance(TimeDelta::FromMinutes(31));
scheduler()->OnSuggestionConsumed();
}
// Many test cases want to ask the scheduler multiple times in a row to see
// which of the different triggers or under which conditions the scheduler
// will request a refresh. However the scheduler updates internal state when
// it decides a refresh must be made, most importantly, it sets
// |tracking_oustanding_request_| to true. Any subsequent trigger would then
// not start a refresh. To get around this, this method clears out
// |tracking_oustanding_request_|.
void ResetRefreshState(Time last_attempt) {
// OnRequestError() has the side effect of setting kLastFetchAttemptTime to
// the scheduler's clock's now. This typically is not helpful to most test
// cases, so override it.
scheduler()->OnRequestError(0);
profile_prefs()->SetTime(prefs::kLastFetchAttemptTime, last_attempt);
}
bool PlatformSupportsEula() {
return web_resource::EulaAcceptedNotifier::Create(local_state()) != nullptr;
}
// This helper method sets prefs::kLastFetchAttemptTime to the same value
// that's about to be passed into ShouldSessionRequestData(). This is what the
// scheduler will typically experience when refreshes are successful. Also
// clears out |tracking_oustanding_request_| through OnRequestError().
NativeRequestBehavior ShouldSessionRequestData(
bool has_content,
Time content_creation_date_time,
bool has_outstanding_request) {
ResetRefreshState(content_creation_date_time);
return scheduler()->ShouldSessionRequestData(
has_content, content_creation_date_time, has_outstanding_request);
}
TestingPrefServiceSimple* profile_prefs() { return &profile_prefs_; }
TestingPrefServiceSimple* local_state() { return &local_state_; }
base::SimpleTestClock* test_clock() { return &test_clock_; }
FeedSchedulerHost* scheduler() { return scheduler_.get(); }
int refresh_call_count() { return refresh_call_count_; }
const std::vector<TimeDelta>& schedule_wake_up_times() {
return schedule_wake_up_times_;
}
int cancel_wake_up_call_count() { return cancel_wake_up_call_count_; }
int fixed_timer_completion_count() { return fixed_timer_completion_count_; }
private:
void TriggerRefresh() { refresh_call_count_++; }
void ScheduleWakeUp(TimeDelta threshold_ms) {
schedule_wake_up_times_.push_back(threshold_ms);
}
void CancelWakeUp() { cancel_wake_up_call_count_++; }
TestingPrefServiceSimple profile_prefs_;
TestingPrefServiceSimple local_state_;
base::SimpleTestClock test_clock_;
std::unique_ptr<FeedSchedulerHost> scheduler_;
int refresh_call_count_ = 0;
std::vector<TimeDelta> schedule_wake_up_times_;
int cancel_wake_up_call_count_ = 0;
int fixed_timer_completion_count_ = 0;
base::WeakPtrFactory<FeedSchedulerHostTest> weak_factory_;
};
TEST_F(FeedSchedulerHostTest, GetTriggerThreshold) {
// Make sure that there is no missing configuration in the Cartesian product
// of states between TriggerType and UserClass.
std::vector<FeedSchedulerHost::TriggerType> triggers = {
FeedSchedulerHost::TriggerType::kNtpShown,
FeedSchedulerHost::TriggerType::kForegrounded,
FeedSchedulerHost::TriggerType::kFixedTimer};
// Classification starts out as an active NTP user.
for (FeedSchedulerHost::TriggerType trigger : triggers) {
EXPECT_FALSE(scheduler()->GetTriggerThreshold(trigger).is_zero());
}
ClassifyAsRareNtpUser();
for (FeedSchedulerHost::TriggerType trigger : triggers) {
EXPECT_FALSE(scheduler()->GetTriggerThreshold(trigger).is_zero());
}
ClassifyAsActiveSuggestionsConsumer();
for (FeedSchedulerHost::TriggerType trigger : triggers) {
EXPECT_FALSE(scheduler()->GetTriggerThreshold(trigger).is_zero());
}
}
TEST_F(FeedSchedulerHostTest, ShouldSessionRequestDataSimple) {
// For an kActiveNtpUser, refreshes on NTP_OPEN should be triggered after 4
// hours, and staleness should be at 24 hours. Each case tests a range of
// values.
Time no_refresh_large = test_clock()->Now() - TimeDelta::FromHours(3);
Time refresh_only_small = test_clock()->Now() - TimeDelta::FromHours(5);
Time refresh_only_large = test_clock()->Now() - TimeDelta::FromHours(23);
Time stale_small = test_clock()->Now() - TimeDelta::FromHours(25);
EXPECT_EQ(kRequestWithWait,
ShouldSessionRequestData(
/*has_content*/ false, /*content_creation_date_time*/ Time(),
/*has_outstanding_request*/ false));
EXPECT_EQ(kRequestWithContent,
ShouldSessionRequestData(
/*has_content*/ true,
/*content_creation_date_time*/ refresh_only_small,
/*has_outstanding_request*/ false));
EXPECT_EQ(kRequestWithContent,
ShouldSessionRequestData(
/*has_content*/ true,
/*content_creation_date_time*/ refresh_only_large,
/*has_outstanding_request*/ false));
EXPECT_EQ(kRequestWithTimeout, ShouldSessionRequestData(
/*has_content*/ true,
/*content_creation_date_time*/ stale_small,
/*has_outstanding_request*/ false));
EXPECT_EQ(kRequestWithTimeout,
ShouldSessionRequestData(
/*has_content*/ true, /*content_creation_date_time*/ Time(),
/*has_outstanding_request*/ false));
// |content_creation_date_time| should be ignored when |has_content| is false.
EXPECT_EQ(kNoRequestWithWait,
ShouldSessionRequestData(
/*has_content*/ false,
/*content_creation_date_time*/ test_clock()->Now(),
/*has_outstanding_request*/ true));
EXPECT_EQ(kNoRequestWithWait,
ShouldSessionRequestData(
/*has_content*/ false, /*content_creation_date_time*/ Time(),
/*has_outstanding_request*/ true));
EXPECT_EQ(kNoRequestWithContent,
ShouldSessionRequestData(
/*has_content*/ true,
/*content_creation_date_time*/ test_clock()->Now(),
/*has_outstanding_request*/ false));
EXPECT_EQ(kNoRequestWithContent,
ShouldSessionRequestData(
/*has_content*/ true,
/*content_creation_date_time*/ no_refresh_large,
/*has_outstanding_request*/ false));
EXPECT_EQ(kNoRequestWithContent,
ShouldSessionRequestData(
/*has_content*/ true,
/*content_creation_date_time*/ test_clock()->Now(),
/*has_outstanding_request*/ true));
EXPECT_EQ(kNoRequestWithContent,
ShouldSessionRequestData(
/*has_content*/ true,
/*content_creation_date_time*/ refresh_only_large,
/*has_outstanding_request*/ true));
EXPECT_EQ(
kNoRequestWithTimeout,
ShouldSessionRequestData(
/*has_content*/ true, /*content_creation_date_time*/ stale_small,
/*has_outstanding_request*/ true));
EXPECT_EQ(kNoRequestWithTimeout,
ShouldSessionRequestData(
/*has_content*/ true, /*content_creation_date_time*/ Time(),
/*has_outstanding_request*/ true));
}
TEST_F(FeedSchedulerHostTest, ShouldSessionRequestDataDivergentTimes) {
// If a request fails, then the |content_creation_date_time| and the value of
// prefs::kLastFetchAttemptTime may diverge. This is okay, and will typically
// mean that refreshes are not taken. Staleness should continue to track
// |content_creation_date_time|, but because staleness uses a bigger threshold
// than NTP_OPEN, this will not affect much.
// Like above case, the user is an kActiveNtpUser, staleness at 24 hours and
// refresh at 4.
Time refresh = test_clock()->Now() - TimeDelta::FromHours(5);
Time no_refresh = test_clock()->Now() - TimeDelta::FromHours(3);
Time stale = test_clock()->Now() - TimeDelta::FromHours(25);
Time not_stale = test_clock()->Now() - TimeDelta::FromHours(23);
ResetRefreshState(no_refresh);
EXPECT_EQ(kNoRequestWithContent, scheduler()->ShouldSessionRequestData(
/*has_content*/ true,
/*content_creation_date_time*/ stale,
/*has_outstanding_request*/ false));
ResetRefreshState(refresh);
EXPECT_EQ(kRequestWithContent, scheduler()->ShouldSessionRequestData(
/*has_content*/ true,
/*content_creation_date_time*/ not_stale,
/*has_outstanding_request*/ false));
ResetRefreshState(refresh);
EXPECT_EQ(kRequestWithTimeout, scheduler()->ShouldSessionRequestData(
/*has_content*/ true,
/*content_creation_date_time*/ stale,
/*has_outstanding_request*/ false));
// This shouldn't be possible, since last attempt is farther back than
// |content_creation_date_time| which updates on success, but verify scheduler
// handles it reasonably.
ResetRefreshState(Time());
EXPECT_EQ(kRequestWithContent,
scheduler()->ShouldSessionRequestData(
/*has_content*/ true,
/*content_creation_date_time*/ test_clock()->Now(),
/*has_outstanding_request*/ false));
// By changing the foregrounded threshold, staleness calculation changes.
variations::testing::VariationParamsManager variation_params(
kInterestFeedContentSuggestions.name,
{{"foregrounded_hours_active_ntp_user", "7.5"}},
{kInterestFeedContentSuggestions.name});
ResetRefreshState(Time());
EXPECT_EQ(kRequestWithContent,
scheduler()->ShouldSessionRequestData(
/*has_content*/ true,
/*content_creation_date_time*/ test_clock()->Now() -
TimeDelta::FromHours(7),
/*has_outstanding_request*/ false));
ResetRefreshState(Time());
EXPECT_EQ(kRequestWithTimeout,
scheduler()->ShouldSessionRequestData(
/*has_content*/ true,
/*content_creation_date_time*/ test_clock()->Now() -
TimeDelta::FromHours(8),
/*has_outstanding_request*/ false));
}
TEST_F(FeedSchedulerHostTest, NtpShownActiveNtpUser) {
variations::testing::VariationParamsManager variation_params(
kInterestFeedContentSuggestions.name,
{{"ntp_shown_hours_active_ntp_user", "2.5"}},
{kInterestFeedContentSuggestions.name});
EXPECT_EQ(kNoRequestWithContent,
ShouldSessionRequestData(
/*has_content*/ true,
/*content_creation_date_time*/ test_clock()->Now() -
TimeDelta::FromHours(2),
/*has_outstanding_request*/ false));
EXPECT_EQ(kRequestWithContent,
ShouldSessionRequestData(
/*has_content*/ true,
/*content_creation_date_time*/ test_clock()->Now() -
TimeDelta::FromHours(3),
/*has_outstanding_request*/ false));
}
TEST_F(FeedSchedulerHostTest, NtpShownRareNtpUser) {
variations::testing::VariationParamsManager variation_params(
kInterestFeedContentSuggestions.name,
{{"ntp_shown_hours_rare_ntp_user", "1.5"}},
{kInterestFeedContentSuggestions.name});
ClassifyAsRareNtpUser();
EXPECT_EQ(kNoRequestWithContent,
ShouldSessionRequestData(
/*has_content*/ true,
/*content_creation_date_time*/ test_clock()->Now() -
TimeDelta::FromHours(1),
/*has_outstanding_request*/ false));
// ShouldSessionRequestData() has the side effect of adding NTP_SHOWN event to
// the classifier, so push the timer out to keep classification.
ClassifyAsRareNtpUser();
EXPECT_EQ(kRequestWithContent,
ShouldSessionRequestData(
/*has_content*/ true,
/*content_creation_date_time*/ test_clock()->Now() -
TimeDelta::FromHours(2),
/*has_outstanding_request*/ false));
}
TEST_F(FeedSchedulerHostTest, NtpShownActiveSuggestionsConsumer) {
ClassifyAsActiveSuggestionsConsumer();
EXPECT_EQ(kNoRequestWithContent,
ShouldSessionRequestData(
/*has_content*/ true,
/*content_creation_date_time*/ test_clock()->Now() -
TimeDelta::FromMinutes(59),
/*has_outstanding_request*/ false));
EXPECT_EQ(kRequestWithContent,
ShouldSessionRequestData(
/*has_content*/ true,
/*content_creation_date_time*/ test_clock()->Now() -
TimeDelta::FromMinutes(61),
/*has_outstanding_request*/ false));
variations::testing::VariationParamsManager variation_params(
kInterestFeedContentSuggestions.name,
{{"ntp_shown_hours_active_suggestions_consumer", "7.5"}},
{kInterestFeedContentSuggestions.name});
EXPECT_EQ(kNoRequestWithContent,
ShouldSessionRequestData(
/*has_content*/ true,
/*content_creation_date_time*/ test_clock()->Now() -
TimeDelta::FromHours(7),
/*has_outstanding_request*/ false));
EXPECT_EQ(kRequestWithContent,
ShouldSessionRequestData(
/*has_content*/ true,
/*content_creation_date_time*/ test_clock()->Now() -
TimeDelta::FromHours(8),
/*has_outstanding_request*/ false));
}
TEST_F(FeedSchedulerHostTest, OnReceiveNewContentVerifyPref) {
EXPECT_EQ(Time(), profile_prefs()->GetTime(prefs::kLastFetchAttemptTime));
scheduler()->OnReceiveNewContent(Time());
EXPECT_EQ(Time(), profile_prefs()->GetTime(prefs::kLastFetchAttemptTime));
// Scheduler should prefer to use specified time over clock time.
EXPECT_NE(test_clock()->Now(),
profile_prefs()->GetTime(prefs::kLastFetchAttemptTime));
}
TEST_F(FeedSchedulerHostTest, OnRequestErrorVerifyPref) {
EXPECT_EQ(Time(), profile_prefs()->GetTime(prefs::kLastFetchAttemptTime));
scheduler()->OnRequestError(0);
EXPECT_EQ(test_clock()->Now(),
profile_prefs()->GetTime(prefs::kLastFetchAttemptTime));
}
TEST_F(FeedSchedulerHostTest, OnForegroundedActiveNtpUser) {
scheduler()->OnForegrounded();
EXPECT_EQ(1, refresh_call_count());
scheduler()->OnReceiveNewContent(test_clock()->Now());
// Default is 24 hours.
test_clock()->Advance(TimeDelta::FromHours(23));
scheduler()->OnForegrounded();
EXPECT_EQ(1, refresh_call_count());
test_clock()->Advance(TimeDelta::FromHours(2));
scheduler()->OnForegrounded();
EXPECT_EQ(2, refresh_call_count());
scheduler()->OnReceiveNewContent(test_clock()->Now());
variations::testing::VariationParamsManager variation_params(
kInterestFeedContentSuggestions.name,
{{"foregrounded_hours_active_ntp_user", "7.5"}},
{kInterestFeedContentSuggestions.name});
test_clock()->Advance(TimeDelta::FromHours(7));
scheduler()->OnForegrounded();
EXPECT_EQ(2, refresh_call_count());
test_clock()->Advance(TimeDelta::FromHours(1));
scheduler()->OnForegrounded();
EXPECT_EQ(3, refresh_call_count());
}
TEST_F(FeedSchedulerHostTest, OnForegroundedRareNtpUser) {
ClassifyAsRareNtpUser();
scheduler()->OnForegrounded();
EXPECT_EQ(1, refresh_call_count());
scheduler()->OnReceiveNewContent(test_clock()->Now());
// Default is 24 hours.
test_clock()->Advance(TimeDelta::FromHours(23));
scheduler()->OnForegrounded();
EXPECT_EQ(1, refresh_call_count());
test_clock()->Advance(TimeDelta::FromHours(2));
scheduler()->OnForegrounded();
EXPECT_EQ(2, refresh_call_count());
scheduler()->OnReceiveNewContent(test_clock()->Now());
variations::testing::VariationParamsManager variation_params(
kInterestFeedContentSuggestions.name,
{{"foregrounded_hours_rare_ntp_user", "7.5"}},
{kInterestFeedContentSuggestions.name});
test_clock()->Advance(TimeDelta::FromHours(7));
scheduler()->OnForegrounded();
EXPECT_EQ(2, refresh_call_count());
test_clock()->Advance(TimeDelta::FromHours(1));
scheduler()->OnForegrounded();
EXPECT_EQ(3, refresh_call_count());
}
TEST_F(FeedSchedulerHostTest, OnForegroundedActiveSuggestionsConsumer) {
ClassifyAsActiveSuggestionsConsumer();
scheduler()->OnForegrounded();
EXPECT_EQ(1, refresh_call_count());
scheduler()->OnReceiveNewContent(test_clock()->Now());
// Default is 12 hours.
test_clock()->Advance(TimeDelta::FromHours(11));
scheduler()->OnForegrounded();
EXPECT_EQ(1, refresh_call_count());
test_clock()->Advance(TimeDelta::FromHours(2));
scheduler()->OnForegrounded();
EXPECT_EQ(2, refresh_call_count());
scheduler()->OnReceiveNewContent(test_clock()->Now());
variations::testing::VariationParamsManager variation_params(
kInterestFeedContentSuggestions.name,
{{"foregrounded_hours_active_suggestions_consumer", "7.5"}},
{kInterestFeedContentSuggestions.name});
test_clock()->Advance(TimeDelta::FromHours(7));
scheduler()->OnForegrounded();
EXPECT_EQ(2, refresh_call_count());
test_clock()->Advance(TimeDelta::FromHours(1));
scheduler()->OnForegrounded();
EXPECT_EQ(3, refresh_call_count());
}
TEST_F(FeedSchedulerHostTest, OnFixedTimerNullCallback) {
scheduler()->OnFixedTimer(base::OnceClosure());
EXPECT_EQ(1, refresh_call_count());
}
TEST_F(FeedSchedulerHostTest, OnFixedTimerCompletionRunOnSuccess) {
scheduler()->OnFixedTimer(base::BindOnce(
&FeedSchedulerHostTest::FixedTimerCompletion, base::Unretained(this)));
EXPECT_EQ(1, refresh_call_count());
scheduler()->OnReceiveNewContent(Time());
EXPECT_EQ(1, fixed_timer_completion_count());
}
TEST_F(FeedSchedulerHostTest, OnFixedTimerCompletionRunOnFailure) {
scheduler()->OnFixedTimer(base::BindOnce(
&FeedSchedulerHostTest::FixedTimerCompletion, base::Unretained(this)));
EXPECT_EQ(1, refresh_call_count());
scheduler()->OnRequestError(0);
EXPECT_EQ(1, fixed_timer_completion_count());
}
TEST_F(FeedSchedulerHostTest, OnFixedTimerActiveNtpUser) {
scheduler()->OnFixedTimer(base::OnceClosure());
EXPECT_EQ(1, refresh_call_count());
scheduler()->OnReceiveNewContent(test_clock()->Now());
// Default is 48 hours.
test_clock()->Advance(TimeDelta::FromHours(47));
scheduler()->OnFixedTimer(base::OnceClosure());
EXPECT_EQ(1, refresh_call_count());
test_clock()->Advance(TimeDelta::FromHours(2));
scheduler()->OnFixedTimer(base::OnceClosure());
EXPECT_EQ(2, refresh_call_count());
scheduler()->OnReceiveNewContent(test_clock()->Now());
variations::testing::VariationParamsManager variation_params(
kInterestFeedContentSuggestions.name,
{{"fixed_timer_hours_active_ntp_user", "7.5"}},
{kInterestFeedContentSuggestions.name});
test_clock()->Advance(TimeDelta::FromHours(7));
scheduler()->OnFixedTimer(base::OnceClosure());
EXPECT_EQ(2, refresh_call_count());
test_clock()->Advance(TimeDelta::FromHours(1));
scheduler()->OnFixedTimer(base::OnceClosure());
EXPECT_EQ(3, refresh_call_count());
}
TEST_F(FeedSchedulerHostTest, OnFixedTimerActiveRareNtpUser) {
ClassifyAsRareNtpUser();
scheduler()->OnFixedTimer(base::OnceClosure());
EXPECT_EQ(1, refresh_call_count());
scheduler()->OnReceiveNewContent(test_clock()->Now());
// Default is 96 hours.
test_clock()->Advance(TimeDelta::FromHours(95));
scheduler()->OnFixedTimer(base::OnceClosure());
EXPECT_EQ(1, refresh_call_count());
test_clock()->Advance(TimeDelta::FromHours(2));
scheduler()->OnFixedTimer(base::OnceClosure());
EXPECT_EQ(2, refresh_call_count());
scheduler()->OnReceiveNewContent(test_clock()->Now());
variations::testing::VariationParamsManager variation_params(
kInterestFeedContentSuggestions.name,
{{"fixed_timer_hours_rare_ntp_user", "7.5"}},
{kInterestFeedContentSuggestions.name});
test_clock()->Advance(TimeDelta::FromHours(7));
scheduler()->OnFixedTimer(base::OnceClosure());
EXPECT_EQ(2, refresh_call_count());
test_clock()->Advance(TimeDelta::FromHours(1));
scheduler()->OnFixedTimer(base::OnceClosure());
EXPECT_EQ(3, refresh_call_count());
}
TEST_F(FeedSchedulerHostTest, OnFixedTimerWhileHidden) {
profile_prefs()->SetBoolean(prefs::kArticlesListVisible, false);
scheduler()->OnFixedTimer(base::OnceClosure());
EXPECT_EQ(0, refresh_call_count());
EXPECT_EQ(1, cancel_wake_up_call_count());
}
TEST_F(FeedSchedulerHostTest, OnFixedTimerActiveSuggestionsConsumer) {
ClassifyAsActiveSuggestionsConsumer();
scheduler()->OnFixedTimer(base::OnceClosure());
EXPECT_EQ(1, refresh_call_count());
scheduler()->OnReceiveNewContent(test_clock()->Now());
// Default is 24 hours.
test_clock()->Advance(TimeDelta::FromHours(23));
scheduler()->OnFixedTimer(base::OnceClosure());
EXPECT_EQ(1, refresh_call_count());
test_clock()->Advance(TimeDelta::FromHours(2));
scheduler()->OnFixedTimer(base::OnceClosure());
EXPECT_EQ(2, refresh_call_count());
scheduler()->OnReceiveNewContent(test_clock()->Now());
variations::testing::VariationParamsManager variation_params(
kInterestFeedContentSuggestions.name,
{{"fixed_timer_hours_active_suggestions_consumer", "7.5"}},
{kInterestFeedContentSuggestions.name});
test_clock()->Advance(TimeDelta::FromHours(7));
scheduler()->OnFixedTimer(base::OnceClosure());
EXPECT_EQ(2, refresh_call_count());
test_clock()->Advance(TimeDelta::FromHours(1));
scheduler()->OnFixedTimer(base::OnceClosure());
EXPECT_EQ(3, refresh_call_count());
}
TEST_F(FeedSchedulerHostTest, ScheduleFixedTimerWakeUpOnSuccess) {
// First wake up scheduled during Initialize().
EXPECT_EQ(1U, schedule_wake_up_times().size());
scheduler()->OnReceiveNewContent(Time());
EXPECT_EQ(2U, schedule_wake_up_times().size());
// Make another scheduler to initialize, make sure it doesn't schedule a
// wake up.
NewScheduler();
EXPECT_EQ(2U, schedule_wake_up_times().size());
}
TEST_F(FeedSchedulerHostTest, InitializedIntoHidden) {
// First wake up scheduled during Initialize().
EXPECT_EQ(1U, schedule_wake_up_times().size());
profile_prefs()->SetBoolean(prefs::kArticlesListVisible, false);
profile_prefs()->ClearPref(prefs::kBackgroundRefreshPeriod);
NewScheduler();
EXPECT_EQ(1U, schedule_wake_up_times().size());
EXPECT_EQ(0, cancel_wake_up_call_count());
}
TEST_F(FeedSchedulerHostTest, InitializedIntoHiddenWithPrevious) {
// First wake up scheduled during Initialize().
EXPECT_EQ(1U, schedule_wake_up_times().size());
profile_prefs()->SetBoolean(prefs::kArticlesListVisible, false);
profile_prefs()->SetTimeDelta(prefs::kBackgroundRefreshPeriod,
base::TimeDelta::FromDays(12345));
NewScheduler();
EXPECT_EQ(1U, schedule_wake_up_times().size());
EXPECT_EQ(1, cancel_wake_up_call_count());
}
TEST_F(FeedSchedulerHostTest, InitializedIntoVisible) {
// First wake up scheduled during Initialize().
EXPECT_EQ(1U, schedule_wake_up_times().size());
profile_prefs()->SetBoolean(prefs::kArticlesListVisible, true);
profile_prefs()->ClearPref(prefs::kBackgroundRefreshPeriod);
NewScheduler();
EXPECT_EQ(2U, schedule_wake_up_times().size());
EXPECT_EQ(0, cancel_wake_up_call_count());
}
TEST_F(FeedSchedulerHostTest, InitializedIntoVisibleWithPrevious) {
// First wake up scheduled during Initialize().
EXPECT_EQ(1U, schedule_wake_up_times().size());
profile_prefs()->SetBoolean(prefs::kArticlesListVisible, true);
profile_prefs()->SetTimeDelta(prefs::kBackgroundRefreshPeriod,
base::TimeDelta::FromDays(12345));
NewScheduler();
EXPECT_EQ(2U, schedule_wake_up_times().size());
EXPECT_EQ(0, cancel_wake_up_call_count());
}
TEST_F(FeedSchedulerHostTest, ShouldRefreshOffline) {
{
ForceDeviceOffline forceDeviceOffline;
scheduler()->OnForegrounded();
EXPECT_EQ(0, refresh_call_count());
}
scheduler()->OnForegrounded();
EXPECT_EQ(1, refresh_call_count());
}
TEST_F(FeedSchedulerHostTest, EulaNotAccepted) {
if (PlatformSupportsEula()) {
local_state()->SetBoolean(::prefs::kEulaAccepted, false);
scheduler()->OnForegrounded();
EXPECT_EQ(0, refresh_call_count());
// The transition should kick off a refresh.
local_state()->SetBoolean(::prefs::kEulaAccepted, true);
EXPECT_EQ(1, refresh_call_count());
// And now it doesn't block normal triggers either.
ResetRefreshState(Time());
scheduler()->OnForegrounded();
EXPECT_EQ(2, refresh_call_count());
}
}
TEST_F(FeedSchedulerHostTest, DisableOneTrigger) {
variations::testing::VariationParamsManager variation_params(
kInterestFeedContentSuggestions.name,
{{kDisableTriggerTypes.name, "foregrounded"}},
{kInterestFeedContentSuggestions.name});
NewScheduler();
scheduler()->OnForegrounded();
EXPECT_EQ(0, refresh_call_count());
scheduler()->OnFixedTimer(base::OnceClosure());
EXPECT_EQ(1, refresh_call_count());
EXPECT_EQ(kRequestWithWait,
ShouldSessionRequestData(
/*has_content*/ false, /*content_creation_date_time*/ Time(),
/*has_outstanding_request*/ false));
}
TEST_F(FeedSchedulerHostTest, DisableAllTriggers) {
variations::testing::VariationParamsManager variation_params(
kInterestFeedContentSuggestions.name,
{{kDisableTriggerTypes.name, "ntp_shown,foregrounded,fixed_timer"}},
{kInterestFeedContentSuggestions.name});
NewScheduler();
scheduler()->OnForegrounded();
EXPECT_EQ(0, refresh_call_count());
scheduler()->OnFixedTimer(base::OnceClosure());
EXPECT_EQ(0, refresh_call_count());
EXPECT_EQ(kNoRequestWithWait,
ShouldSessionRequestData(
/*has_content*/ false, /*content_creation_date_time*/ Time(),
/*has_outstanding_request*/ false));
}
TEST_F(FeedSchedulerHostTest, DisableBogusTriggers) {
variations::testing::VariationParamsManager variation_params(
kInterestFeedContentSuggestions.name,
{{kDisableTriggerTypes.name, "foo,123,#$*,,"}},
{kInterestFeedContentSuggestions.name});
NewScheduler();
scheduler()->OnForegrounded();
EXPECT_EQ(1, refresh_call_count());
ResetRefreshState(Time());
scheduler()->OnFixedTimer(base::OnceClosure());
EXPECT_EQ(2, refresh_call_count());
EXPECT_EQ(kRequestWithWait,
ShouldSessionRequestData(
/*has_content*/ false, /*content_creation_date_time*/ Time(),
/*has_outstanding_request*/ false));
}
TEST_F(FeedSchedulerHostTest, OnArticlesCleared) {
// OnForegrounded() does nothing because content is fresher than threshold.
scheduler()->OnReceiveNewContent(test_clock()->Now());
scheduler()->OnForegrounded();
EXPECT_EQ(0, refresh_call_count());
EXPECT_FALSE(scheduler()->OnArticlesCleared(/*suppress_refreshes*/ true));
scheduler()->OnForegrounded();
EXPECT_EQ(0, refresh_call_count());
scheduler()->OnFixedTimer(base::OnceClosure());
EXPECT_EQ(0, refresh_call_count());
EXPECT_EQ(kNoRequestWithWait,
ShouldSessionRequestData(
/*has_content*/ false, /*content_creation_date_time*/ Time(),
/*has_outstanding_request*/ false));
test_clock()->Advance(TimeDelta::FromMinutes(29));
scheduler()->OnForegrounded();
EXPECT_EQ(0, refresh_call_count());
test_clock()->Advance(TimeDelta::FromMinutes(1));
// Normally this would still be within foreground threshold, but the
// OnHistoryCleared() cleared the last attempt time.
scheduler()->OnForegrounded();
EXPECT_EQ(1, refresh_call_count());
}
TEST_F(FeedSchedulerHostTest, SuppressRefreshDuration) {
variations::testing::VariationParamsManager variation_params(
kInterestFeedContentSuggestions.name,
{{kSuppressRefreshDurationMinutes.name, "100"}},
{kInterestFeedContentSuggestions.name});
EXPECT_FALSE(scheduler()->OnArticlesCleared(/*suppress_refreshes*/ true));
test_clock()->Advance(TimeDelta::FromMinutes(99));
scheduler()->OnForegrounded();
EXPECT_EQ(0, refresh_call_count());
test_clock()->Advance(TimeDelta::FromMinutes(1));
scheduler()->OnForegrounded();
EXPECT_EQ(1, refresh_call_count());
}
TEST_F(FeedSchedulerHostTest, OnArticlesClearedNoSuppress) {
EXPECT_TRUE(scheduler()->OnArticlesCleared(/*suppress_refreshes*/ false));
scheduler()->OnReceiveNewContent(test_clock()->Now());
EXPECT_TRUE(scheduler()->OnArticlesCleared(/*suppress_refreshes*/ false));
scheduler()->OnReceiveNewContent(test_clock()->Now());
EXPECT_FALSE(scheduler()->OnArticlesCleared(/*suppress_refreshes*/ true));
EXPECT_FALSE(scheduler()->OnArticlesCleared(/*suppress_refreshes*/ false));
// OnArticlesCleared() should never trigger the refresh itself.
EXPECT_EQ(0, refresh_call_count());
}
TEST_F(FeedSchedulerHostTest, OnArticlesClearedIgnoresOutstanding) {
scheduler()->OnForegrounded();
EXPECT_EQ(1, refresh_call_count());
// Now that there's an outstanding request, new triggers are not acted upon.
scheduler()->OnForegrounded();
EXPECT_EQ(1, refresh_call_count());
// Clearing articles should disregard the outstanding request logic.
EXPECT_TRUE(scheduler()->OnArticlesCleared(/*suppress_refreshes*/ false));
// OnArticlesCleared() should have returned true instead of triggering a
// refresh directly.
EXPECT_EQ(1, refresh_call_count());
}
TEST_F(FeedSchedulerHostTest, OustandingRequest) {
scheduler()->OnForegrounded();
EXPECT_EQ(1, refresh_call_count());
scheduler()->OnForegrounded();
scheduler()->OnFixedTimer(base::OnceClosure());
EXPECT_EQ(1, refresh_call_count());
EXPECT_EQ(kNoRequestWithWait,
scheduler()->ShouldSessionRequestData(
/*has_content*/ false, /*content_creation_date_time*/ Time(),
/*has_outstanding_request*/ true));
profile_prefs()->SetTime(prefs::kLastFetchAttemptTime, base::Time());
scheduler()->OnForegrounded();
EXPECT_EQ(1, refresh_call_count());
test_clock()->Advance(
TimeDelta::FromSeconds(kTimeoutDurationSeconds.Get() - 1));
scheduler()->OnForegrounded();
EXPECT_EQ(1, refresh_call_count());
test_clock()->Advance(TimeDelta::FromSeconds(2));
scheduler()->OnForegrounded();
EXPECT_EQ(2, refresh_call_count());
// OnReceiveNewContent() should also clear tracked outstanding request, but
// similar to above, last attempted time is also set.
scheduler()->OnReceiveNewContent(test_clock()->Now());
scheduler()->OnForegrounded();
EXPECT_EQ(2, refresh_call_count());
test_clock()->Advance(TimeDelta::FromDays(7));
scheduler()->OnForegrounded();
EXPECT_EQ(3, refresh_call_count());
// Although this shouldn't typically happen, OnReceiveNewContent() takes a
// time that could be wildly divergent from the scheduler's clock's now.
scheduler()->OnReceiveNewContent(test_clock()->Now() -
TimeDelta::FromDays(7));
scheduler()->OnForegrounded();
EXPECT_EQ(4, refresh_call_count());
}
TEST_F(FeedSchedulerHostTest, IncorporatesExternalOustandingRequest) {
EXPECT_EQ(kNoRequestWithWait,
scheduler()->ShouldSessionRequestData(
/*has_content*/ false, /*content_creation_date_time*/ Time(),
/*has_outstanding_request*/ true));
// Normally this would trigger a refresh. In this case the scheduler will
// notice the ShouldSessionRequestData() call carries information that there
// is an outstanding request, which the scheduler should remember and then
// prevent the OnForegrounded() from requesting a refresh.
scheduler()->OnForegrounded();
EXPECT_EQ(0, refresh_call_count());
EXPECT_EQ(kRequestWithWait,
scheduler()->ShouldSessionRequestData(
/*has_content*/ false, /*content_creation_date_time*/ Time(),
/*has_outstanding_request*/ false));
}
TEST_F(FeedSchedulerHostTest, IncorporatesExternalHasContent) {
Time now = test_clock()->Now();
EXPECT_EQ(Time(), profile_prefs()->GetTime(prefs::kLastFetchAttemptTime));
EXPECT_EQ(kNoRequestWithContent,
scheduler()->ShouldSessionRequestData(
/*has_content*/ true, now, /*has_outstanding_request*/ false));
EXPECT_EQ(now, profile_prefs()->GetTime(prefs::kLastFetchAttemptTime));
// Use has_outstanding_request of true to keep the scheduler from actually
// triggering the refresh. We want to track the change to its internal state.
EXPECT_EQ(kNoRequestWithWait, scheduler()->ShouldSessionRequestData(
/*has_content*/ false, base::Time(),
/*has_outstanding_request*/ true));
EXPECT_EQ(Time(), profile_prefs()->GetTime(prefs::kLastFetchAttemptTime));
}
TEST_F(FeedSchedulerHostTest, TimeUntilFirstMetrics) {
base::HistogramTester histogram_tester;
std::string ntpOpenedHistogram =
"NewTabPage.ContentSuggestions.TimeUntilFirstShownTrigger.ActiveNTPUser";
std::string forgroundedHistogram =
"NewTabPage.ContentSuggestions.TimeUntilFirstStartupTrigger"
".ActiveNTPUser";
Time now = test_clock()->Now();
profile_prefs()->SetTime(prefs::kLastFetchAttemptTime, now);
EXPECT_EQ(0U, histogram_tester.GetAllSamples(ntpOpenedHistogram).size());
EXPECT_EQ(0U, histogram_tester.GetAllSamples(forgroundedHistogram).size());
scheduler()->ShouldSessionRequestData(
/*has_content*/ true, now, /*has_outstanding_request*/ false);
EXPECT_EQ(1, histogram_tester.GetBucketCount(ntpOpenedHistogram, 0));
EXPECT_EQ(0U, histogram_tester.GetAllSamples(forgroundedHistogram).size());
scheduler()->OnForegrounded();
EXPECT_EQ(1, histogram_tester.GetBucketCount(ntpOpenedHistogram, 0));
EXPECT_EQ(1, histogram_tester.GetBucketCount(forgroundedHistogram, 0));
scheduler()->ShouldSessionRequestData(
/*has_content*/ true, now, /*has_outstanding_request*/ false);
scheduler()->OnForegrounded();
EXPECT_EQ(1, histogram_tester.GetBucketCount(ntpOpenedHistogram, 0));
EXPECT_EQ(1, histogram_tester.GetBucketCount(forgroundedHistogram, 0));
// OnRequestError() should reset the flags, allowing these metrics to be
// reported again.
scheduler()->OnRequestError(0);
scheduler()->ShouldSessionRequestData(
/*has_content*/ true, now, /*has_outstanding_request*/ false);
scheduler()->OnForegrounded();
EXPECT_EQ(2, histogram_tester.GetBucketCount(ntpOpenedHistogram, 0));
EXPECT_EQ(2, histogram_tester.GetBucketCount(forgroundedHistogram, 0));
}
TEST_F(FeedSchedulerHostTest, RefreshThrottler) {
variations::testing::VariationParamsManager variation_params(
kInterestFeedContentSuggestions.name,
{{"quota_SuggestionFetcherActiveNTPUser", "3"}},
{kInterestFeedContentSuggestions.name});
NewScheduler();
for (int i = 0; i < 5; i++) {
scheduler()->OnForegrounded();
ResetRefreshState(Time());
EXPECT_EQ(std::min(i + 1, 3), refresh_call_count());
}
}
} // namespace feed