blob: e7061dc47f5aece9ec08a634eabd64a0b35cdf18 [file] [log] [blame]
// Copyright 2016 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/offline_pages/core/background/request_coordinator.h"
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/containers/circular_deque.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/synchronization/waitable_event.h"
#include "base/system/sys_info.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_mock_time_task_runner.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "components/offline_items_collection/core/pending_state.h"
#include "components/offline_pages/core/background/device_conditions.h"
#include "components/offline_pages/core/background/offliner.h"
#include "components/offline_pages/core/background/offliner_policy.h"
#include "components/offline_pages/core/background/offliner_stub.h"
#include "components/offline_pages/core/background/request_coordinator_stub_taco.h"
#include "components/offline_pages/core/background/request_queue.h"
#include "components/offline_pages/core/background/request_queue_store.h"
#include "components/offline_pages/core/background/save_page_request.h"
#include "components/offline_pages/core/background/scheduler.h"
#include "components/offline_pages/core/background/scheduler_stub.h"
#include "components/offline_pages/core/client_namespace_constants.h"
#include "components/offline_pages/core/offline_clock.h"
#include "components/offline_pages/core/offline_page_feature.h"
#include "services/network/test/test_network_quality_tracker.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace offline_pages {
namespace {
// put test constants here
const GURL kUrl1("http://universe.com/everything");
const GURL kUrl2("http://universe.com/toinfinityandbeyond");
const std::string kClientNamespace("bookmark");
const std::string kId1("42");
const std::string kId2("life*universe+everything");
const ClientId kClientId1(kClientNamespace, kId1);
const ClientId kClientId2(kClientNamespace, kId2);
const int kRequestId1(1);
const int kRequestId2(2);
const long kTestTimeBudgetSeconds = 200;
const int kBatteryPercentageHigh = 75;
const int kMaxCompletedTries = 3;
const bool kPowerRequired = true;
const bool kUserRequested = true;
const int kAttemptCount = 1;
const std::string kRequestOrigin("abc.xyz");
class ObserverStub : public RequestCoordinator::Observer {
public:
ObserverStub()
: added_called_(false),
completed_called_(false),
changed_called_(false),
network_progress_called_(false),
network_progress_bytes_(0),
last_status_(RequestCoordinator::BackgroundSavePageResult::SUCCESS),
state_(SavePageRequest::RequestState::OFFLINING) {}
void Clear() {
added_called_ = false;
completed_called_ = false;
changed_called_ = false;
network_progress_called_ = false;
network_progress_bytes_ = 0;
state_ = SavePageRequest::RequestState::OFFLINING;
last_status_ = RequestCoordinator::BackgroundSavePageResult::SUCCESS;
}
void OnAdded(const SavePageRequest& request) override {
added_called_ = true;
state_ = request.request_state();
pending_state_ = request.pending_state();
}
void OnCompleted(
const SavePageRequest& request,
RequestCoordinator::BackgroundSavePageResult status) override {
completed_called_ = true;
last_status_ = status;
}
void OnChanged(const SavePageRequest& request) override {
changed_called_ = true;
state_ = request.request_state();
pending_state_ = request.pending_state();
}
void OnNetworkProgress(const SavePageRequest& request,
int64_t received_bytes) override {
network_progress_called_ = true;
network_progress_bytes_ = received_bytes;
}
bool added_called() { return added_called_; }
bool completed_called() { return completed_called_; }
bool changed_called() { return changed_called_; }
bool network_progress_called() { return network_progress_called_; }
int64_t network_progress_bytes() { return network_progress_bytes_; }
RequestCoordinator::BackgroundSavePageResult last_status() {
return last_status_;
}
SavePageRequest::RequestState state() { return state_; }
PendingState pending_state() { return pending_state_; }
private:
bool added_called_;
bool completed_called_;
bool changed_called_;
bool network_progress_called_;
int64_t network_progress_bytes_;
RequestCoordinator::BackgroundSavePageResult last_status_;
SavePageRequest::RequestState state_;
PendingState pending_state_;
};
class ActiveTabInfoStub : public RequestCoordinator::ActiveTabInfo {
public:
~ActiveTabInfoStub() override {}
bool DoesActiveTabMatch(const GURL&) override {
return does_active_tab_match_;
}
void set_does_active_tab_match(bool match) { does_active_tab_match_ = match; }
private:
bool does_active_tab_match_ = false;
};
} // namespace
// This class is a friend of RequestCoordinator, and can't be in the anonymous
// namespace.
class RequestCoordinatorTest : public testing::Test {
public:
using RequestCoordinatorState = RequestCoordinator::RequestCoordinatorState;
RequestCoordinatorTest();
~RequestCoordinatorTest() override;
void SetUp() override;
void TearDown() override {
PumpLoop();
coordinator_taco_.reset();
// Ensure cleanup tasks are complete, or we may leak memory.
task_runner_->FastForwardUntilNoTasksRemain();
}
void PumpLoop();
RequestCoordinator* coordinator() const {
return coordinator_taco_->request_coordinator();
}
OfflinerStub* offliner() const { return offliner_; }
SchedulerStub* scheduler_stub() const {
return reinterpret_cast<SchedulerStub*>(coordinator()->scheduler());
}
RequestQueue* queue() {
return coordinator_taco_->request_coordinator()->queue_for_testing();
}
RequestCoordinatorState state() { return coordinator()->state(); }
// Test processing callback function.
void ProcessingCallbackFunction(bool result) {
processing_callback_called_ = true;
processing_callback_result_ = result;
}
// Callback function which releases a wait for it.
void WaitingCallbackFunction(bool result) { waiter_.Signal(); }
net::NetworkChangeNotifier::ConnectionType GetConnectionType() {
return coordinator()->current_conditions_->GetNetConnectionType();
}
// Callback for Add requests.
void AddRequestDone(AddRequestResult result, const SavePageRequest& request);
// Callback for getting requests.
void GetRequestsDone(GetRequestsResult result,
std::vector<std::unique_ptr<SavePageRequest>> requests);
// Callback for removing requests.
void RemoveRequestsDone(const MultipleItemStatuses& results);
// Callback for getting request statuses.
void GetQueuedRequestsDone(
std::vector<std::unique_ptr<SavePageRequest>> requests);
void SetupForOfflinerDoneCallbackTest(
offline_pages::SavePageRequest* request);
void SendOfflinerDoneCallback(const SavePageRequest& request,
Offliner::RequestStatus status);
GetRequestsResult last_get_requests_result() const {
return last_get_requests_result_;
}
const std::vector<std::unique_ptr<SavePageRequest>>& last_requests() const {
return last_requests_;
}
const MultipleItemStatuses& last_remove_results() const {
return last_remove_results_;
}
void DisableLoading() { offliner_->disable_loading(); }
void EnableOfflinerCallback(bool enable) {
offliner_->enable_callback(enable);
}
void EnableSnapshotOnLastRetry() {
offliner_->enable_snapshot_on_last_retry();
}
bool OfflinerCalled() const { return offliner_->load_and_save_called(); }
void SetEffectiveConnectionTypeForTest(net::EffectiveConnectionType type) {
network_quality_tracker_->ReportEffectiveConnectionTypeForTesting(type);
}
void SetNetworkConnected(bool connected) {
if (connected) {
DeviceConditions device_conditions(
!kPowerRequired, kBatteryPercentageHigh,
net::NetworkChangeNotifier::CONNECTION_3G);
SetDeviceConditionsForTest(device_conditions);
SetEffectiveConnectionTypeForTest(
net::EffectiveConnectionType::EFFECTIVE_CONNECTION_TYPE_3G);
} else {
DeviceConditions device_conditions(
!kPowerRequired, kBatteryPercentageHigh,
net::NetworkChangeNotifier::CONNECTION_NONE);
SetDeviceConditionsForTest(device_conditions);
SetEffectiveConnectionTypeForTest(
net::EffectiveConnectionType::EFFECTIVE_CONNECTION_TYPE_OFFLINE);
}
}
void CallConnectionTypeObserver() {
if (coordinator()->connection_notifier_) {
coordinator()->connection_notifier_->OnNetworkChanged(
GetConnectionType());
}
}
void SetIsLowEndDeviceForTest(bool is_low_end_device) {
coordinator()->is_low_end_device_ = is_low_end_device;
}
void SetProcessingStateForTest(
RequestCoordinator::ProcessingWindowState processing_state) {
coordinator()->processing_state_ = processing_state;
}
void SetOperationStartTimeForTest(base::Time start_time) {
coordinator()->operation_start_time_ = start_time;
}
void ScheduleForTest() { coordinator()->ScheduleAsNeeded(); }
void CallRequestNotPicked(bool non_user_requested_tasks_remaining,
bool disabled_tasks_remaining) {
if (disabled_tasks_remaining)
coordinator()->disabled_requests_.insert(kRequestId1);
else
coordinator()->disabled_requests_.clear();
coordinator()->RequestNotPicked(non_user_requested_tasks_remaining, false,
base::Time());
}
void SetDeviceConditionsForTest(DeviceConditions device_conditions) {
coordinator()->SetDeviceConditionsForTest(device_conditions);
}
DeviceConditions* GetDeviceConditions() {
return coordinator()->current_conditions_.get();
}
void WaitForCallback() { waiter_.Wait(); }
void AdvanceClockBy(base::TimeDelta delta) {
task_runner_->FastForwardBy(delta);
}
SavePageRequest AddRequest1();
SavePageRequest AddRequest2();
void SavePageRequestCallback(AddRequestResult result) {
ASSERT_EQ(expected_add_request_result_, result);
add_request_callback_called_ = true;
}
int64_t SavePageLater() {
RequestCoordinator::SavePageLaterParams params;
params.url = kUrl1;
params.client_id = kClientId1;
params.user_requested = kUserRequested;
params.request_origin = kRequestOrigin;
return coordinator()->SavePageLater(
params, base::BindOnce(&RequestCoordinatorTest::SavePageRequestCallback,
base::Unretained(this)));
}
int64_t SavePageLaterWithAvailability(
RequestCoordinator::RequestAvailability availability) {
RequestCoordinator::SavePageLaterParams params;
params.url = kUrl1;
params.client_id = kClientId1;
params.user_requested = kUserRequested;
params.availability = availability;
params.request_origin = kRequestOrigin;
return coordinator()->SavePageLater(
params, base::BindOnce(&RequestCoordinatorTest::SavePageRequestCallback,
base::Unretained(this)));
}
Offliner::RequestStatus last_offlining_status() const {
return coordinator()->last_offlining_status_;
}
// Calls the private method |StopProcessing| on |coordinator()|.
void StopProcessing(Offliner::RequestStatus stop_status) {
coordinator()->StopProcessing(stop_status);
}
bool OfflinerWasCanceled() const { return offliner_->cancel_called(); }
void ResetOfflinerWasCanceled() { offliner_->reset_cancel_called(); }
ObserverStub observer() { return observer_; }
DeviceConditions device_conditions() { return device_conditions_; }
base::RepeatingCallback<void(bool)> processing_callback() {
return processing_callback_;
}
base::RepeatingCallback<void(bool)> waiting_callback() {
return waiting_callback_;
}
bool processing_callback_called() const {
return processing_callback_called_;
}
bool processing_callback_result() const {
return processing_callback_result_;
}
const base::HistogramTester& histograms() const { return histogram_tester_; }
const std::set<int64_t>& disabled_requests() {
return coordinator()->disabled_requests_;
}
const base::circular_deque<int64_t>& prioritized_requests() {
return coordinator()->prioritized_requests_;
}
bool add_request_callback_called() { return add_request_callback_called_; }
scoped_refptr<base::TestMockTimeTaskRunner> task_runner() const {
return task_runner_;
}
protected:
ActiveTabInfoStub* active_tab_info_ = nullptr;
private:
GetRequestsResult last_get_requests_result_;
MultipleItemStatuses last_remove_results_;
std::vector<std::unique_ptr<SavePageRequest>> last_requests_;
scoped_refptr<base::TestMockTimeTaskRunner> task_runner_;
base::ThreadTaskRunnerHandle task_runner_handle_;
network::NetworkQualityTracker* network_quality_tracker_;
std::unique_ptr<RequestCoordinatorStubTaco> coordinator_taco_;
OfflinerStub* offliner_;
base::WaitableEvent waiter_;
ObserverStub observer_;
AddRequestResult expected_add_request_result_;
bool add_request_callback_called_;
bool processing_callback_called_;
bool processing_callback_result_;
DeviceConditions device_conditions_;
base::RepeatingCallback<void(bool)> processing_callback_;
base::RepeatingCallback<void(bool)> waiting_callback_;
base::HistogramTester histogram_tester_;
};
RequestCoordinatorTest::RequestCoordinatorTest()
: last_get_requests_result_(GetRequestsResult::STORE_FAILURE),
task_runner_(new base::TestMockTimeTaskRunner),
task_runner_handle_(task_runner_),
offliner_(nullptr),
waiter_(base::WaitableEvent::ResetPolicy::MANUAL,
base::WaitableEvent::InitialState::NOT_SIGNALED),
expected_add_request_result_(AddRequestResult::SUCCESS),
add_request_callback_called_(false),
processing_callback_called_(false),
processing_callback_result_(false),
device_conditions_(!kPowerRequired,
kBatteryPercentageHigh,
net::NetworkChangeNotifier::CONNECTION_3G) {}
RequestCoordinatorTest::~RequestCoordinatorTest() {}
void RequestCoordinatorTest::SetUp() {
coordinator_taco_ = std::make_unique<RequestCoordinatorStubTaco>();
std::unique_ptr<OfflinerStub> offliner(new OfflinerStub());
// Save raw pointer for use by the tests.
offliner_ = offliner.get();
coordinator_taco_->SetOffliner(std::move(offliner));
std::unique_ptr<network::NetworkQualityTracker> test_network_quality_tracker(
std::make_unique<network::TestNetworkQualityTracker>());
// Save raw pointer for use by the tests.
network_quality_tracker_ = test_network_quality_tracker.get();
coordinator_taco_->SetNetworkQualityProvider(
std::move(test_network_quality_tracker));
auto delegate = std::make_unique<ActiveTabInfoStub>();
active_tab_info_ = delegate.get();
coordinator_taco_->SetRequestCoordinatorDelegate(std::move(delegate));
coordinator_taco_->CreateRequestCoordinator();
coordinator()->AddObserver(&observer_);
SetNetworkConnected(true);
processing_callback_ =
base::BindRepeating(&RequestCoordinatorTest::ProcessingCallbackFunction,
base::Unretained(this));
// Override the normal immediate callback with a wait releasing callback.
waiting_callback_ = base::BindRepeating(
&RequestCoordinatorTest::WaitingCallbackFunction, base::Unretained(this));
SetDeviceConditionsForTest(device_conditions_);
// Ensure not low-end device so immediate start can happen for most tests.
SetIsLowEndDeviceForTest(false);
EnableOfflinerCallback(true);
}
void RequestCoordinatorTest::PumpLoop() {
task_runner_->RunUntilIdle();
}
void RequestCoordinatorTest::GetRequestsDone(
GetRequestsResult result,
std::vector<std::unique_ptr<SavePageRequest>> requests) {
last_get_requests_result_ = result;
last_requests_ = std::move(requests);
}
void RequestCoordinatorTest::RemoveRequestsDone(
const MultipleItemStatuses& results) {
last_remove_results_ = results;
waiter_.Signal();
}
void RequestCoordinatorTest::GetQueuedRequestsDone(
std::vector<std::unique_ptr<SavePageRequest>> requests) {
last_requests_ = std::move(requests);
waiter_.Signal();
}
void RequestCoordinatorTest::AddRequestDone(AddRequestResult result,
const SavePageRequest& request) {}
void RequestCoordinatorTest::SetupForOfflinerDoneCallbackTest(
offline_pages::SavePageRequest* request) {
// Mark request as started and add it to the queue,
// then wait for callback to finish.
request->MarkAttemptStarted(OfflineTimeNow());
queue()->AddRequest(*request,
base::BindOnce(&RequestCoordinatorTest::AddRequestDone,
base::Unretained(this)));
PumpLoop();
// Override the processing callback for test visiblity.
base::RepeatingCallback<void(bool)> callback =
base::BindRepeating(&RequestCoordinatorTest::ProcessingCallbackFunction,
base::Unretained(this));
coordinator()->SetProcessingCallbackForTest(callback);
// Mock that coordinator is in actively processing state starting now.
SetProcessingStateForTest(
RequestCoordinator::ProcessingWindowState::IMMEDIATE_WINDOW);
SetOperationStartTimeForTest(OfflineTimeNow());
}
void RequestCoordinatorTest::SendOfflinerDoneCallback(
const SavePageRequest& request,
Offliner::RequestStatus status) {
// Using the fact that the test class is a friend, call to the callback
coordinator()->OfflinerDoneCallback(request, status);
}
SavePageRequest RequestCoordinatorTest::AddRequest1() {
offline_pages::SavePageRequest request1(kRequestId1, kUrl1, kClientId1,
OfflineTimeNow(), kUserRequested);
queue()->AddRequest(request1,
base::BindOnce(&RequestCoordinatorTest::AddRequestDone,
base::Unretained(this)));
return request1;
}
SavePageRequest RequestCoordinatorTest::AddRequest2() {
offline_pages::SavePageRequest request2(kRequestId2, kUrl2, kClientId2,
OfflineTimeNow(), kUserRequested);
queue()->AddRequest(request2,
base::BindOnce(&RequestCoordinatorTest::AddRequestDone,
base::Unretained(this)));
return request2;
}
TEST_F(RequestCoordinatorTest, StartScheduledProcessingWithNoRequests) {
// Set low-end device status to actual status.
SetIsLowEndDeviceForTest(base::SysInfo::IsLowEndDevice());
EXPECT_TRUE(coordinator()->StartScheduledProcessing(device_conditions(),
processing_callback()));
PumpLoop();
EXPECT_TRUE(processing_callback_called());
// Verify queue depth UMA for starting scheduled processing on empty queue.
if (base::SysInfo::IsLowEndDevice()) {
histograms().ExpectBucketCount(
"OfflinePages.Background.ScheduledStart.AvailableRequestCount.Svelte",
0, 1);
} else {
histograms().ExpectBucketCount(
"OfflinePages.Background.ScheduledStart.AvailableRequestCount", 0, 1);
}
}
TEST_F(RequestCoordinatorTest, NetworkProgressCallback) {
EXPECT_NE(0, SavePageLater());
PumpLoop();
EXPECT_TRUE(observer().network_progress_called());
EXPECT_GT(observer().network_progress_bytes(), 0LL);
}
TEST_F(RequestCoordinatorTest, StartScheduledProcessingWithRequestInProgress) {
// Start processing for this request.
EXPECT_NE(0, SavePageLater());
// Ensure that the forthcoming request does not finish - we simulate it being
// in progress by asking it to skip making the completion callback.
EnableOfflinerCallback(false);
// Sending the request to the offliner should make it busy.
EXPECT_TRUE(coordinator()->StartScheduledProcessing(device_conditions(),
processing_callback()));
PumpLoop();
EXPECT_TRUE(state() == RequestCoordinatorState::OFFLINING);
// Since the offliner is disabled, this callback should not be called.
EXPECT_FALSE(processing_callback_called());
// Now trying to start processing should return false since already busy.
EXPECT_FALSE(coordinator()->StartScheduledProcessing(device_conditions(),
processing_callback()));
}
TEST_F(RequestCoordinatorTest, StartImmediateProcessingWithNoRequests) {
EXPECT_TRUE(coordinator()->StartImmediateProcessing(processing_callback()));
PumpLoop();
EXPECT_TRUE(processing_callback_called());
histograms().ExpectBucketCount("OfflinePages.Background.ImmediateStartStatus",
0 /* STARTED */, 1);
}
TEST_F(RequestCoordinatorTest, StartImmediateProcessingOnSvelte) {
// Set as low-end device to verfiy immediate processing will not start.
SetIsLowEndDeviceForTest(true);
EXPECT_FALSE(coordinator()->StartImmediateProcessing(processing_callback()));
histograms().ExpectBucketCount("OfflinePages.Background.ImmediateStartStatus",
5 /* NOT_STARTED_ON_SVELTE */, 1);
}
TEST_F(RequestCoordinatorTest, StartImmediateProcessingWhenDisconnected) {
DeviceConditions disconnected_conditions(
!kPowerRequired, kBatteryPercentageHigh,
net::NetworkChangeNotifier::CONNECTION_NONE);
SetDeviceConditionsForTest(disconnected_conditions);
EXPECT_FALSE(coordinator()->StartImmediateProcessing(processing_callback()));
histograms().ExpectBucketCount("OfflinePages.Background.ImmediateStartStatus",
3 /* NO_CONNECTION */, 1);
}
TEST_F(RequestCoordinatorTest, StartImmediateProcessingWithRequestInProgress) {
// Start processing for this request.
EXPECT_NE(0, SavePageLater());
// Disable the automatic offliner callback.
EnableOfflinerCallback(false);
// Sending the request to the offliner should make it busy.
EXPECT_TRUE(coordinator()->StartImmediateProcessing(processing_callback()));
PumpLoop();
EXPECT_TRUE(state() == RequestCoordinatorState::OFFLINING);
// Since the offliner is disabled, this callback should not be called.
EXPECT_FALSE(processing_callback_called());
// Now trying to start processing should return false since already busy.
EXPECT_FALSE(coordinator()->StartImmediateProcessing(processing_callback()));
histograms().ExpectBucketCount("OfflinePages.Background.ImmediateStartStatus",
1 /* BUSY */, 1);
}
TEST_F(RequestCoordinatorTest, SavePageLater) {
// The user-requested request which gets processed by SavePageLater
// would invoke user request callback.
coordinator()->SetInternalStartProcessingCallbackForTest(
processing_callback());
// Use default values for |user_requested| and |availability|.
RequestCoordinator::SavePageLaterParams params;
params.url = kUrl1;
params.client_id = kClientId1;
params.original_url = kUrl2;
params.request_origin = kRequestOrigin;
EXPECT_NE(0, coordinator()->SavePageLater(
params, base::BindOnce(
&RequestCoordinatorTest::SavePageRequestCallback,
base::Unretained(this))));
// Expect that a request got placed on the queue.
queue()->GetRequests(base::BindOnce(&RequestCoordinatorTest::GetRequestsDone,
base::Unretained(this)));
// Expect that the request is not added to the disabled list by default.
EXPECT_TRUE(disabled_requests().empty());
// Wait for callbacks to finish, both request queue and offliner.
PumpLoop();
EXPECT_TRUE(processing_callback_called());
EXPECT_TRUE(add_request_callback_called());
// Check the request queue is as expected.
ASSERT_EQ(1UL, last_requests().size());
EXPECT_EQ(kUrl1, last_requests().at(0)->url());
EXPECT_EQ(kClientId1, last_requests().at(0)->client_id());
EXPECT_TRUE(last_requests().at(0)->user_requested());
EXPECT_EQ(kUrl2, last_requests().at(0)->original_url());
EXPECT_EQ(kRequestOrigin, last_requests().at(0)->request_origin());
// Expect that the scheduler got notified.
EXPECT_TRUE(scheduler_stub()->schedule_called());
EXPECT_EQ(coordinator()
->GetTriggerConditions(last_requests()[0]->user_requested())
.minimum_battery_percentage,
scheduler_stub()->trigger_conditions()->minimum_battery_percentage);
// Check that the observer got the notification that a page is available
EXPECT_TRUE(observer().added_called());
// Verify queue depth UMA for starting immediate processing.
histograms().ExpectBucketCount(
"OfflinePages.Background.ImmediateStart.AvailableRequestCount", 1, 1);
}
TEST_F(RequestCoordinatorTest, SavePageLaterFailed) {
// Set low-end device status to actual status.
SetIsLowEndDeviceForTest(base::SysInfo::IsLowEndDevice());
// The user-requested request which gets processed by SavePageLater
// would invoke user request callback.
coordinator()->SetInternalStartProcessingCallbackForTest(
processing_callback());
EXPECT_NE(0, SavePageLater());
// Expect that a request got placed on the queue.
queue()->GetRequests(base::BindOnce(&RequestCoordinatorTest::GetRequestsDone,
base::Unretained(this)));
// Wait for callbacks to finish, both request queue and offliner.
PumpLoop();
// On low-end devices the callback will be called with false since the
// processing started but failed due to svelte devices.
EXPECT_TRUE(processing_callback_called());
if (base::SysInfo::IsLowEndDevice()) {
EXPECT_FALSE(processing_callback_result());
} else {
EXPECT_TRUE(processing_callback_result());
}
EXPECT_TRUE(add_request_callback_called());
// Check the request queue is as expected.
EXPECT_EQ(1UL, last_requests().size());
EXPECT_EQ(kUrl1, last_requests().at(0)->url());
EXPECT_EQ(kClientId1, last_requests().at(0)->client_id());
// Expect that the scheduler got notified.
EXPECT_TRUE(scheduler_stub()->schedule_called());
EXPECT_EQ(coordinator()
->GetTriggerConditions(last_requests()[0]->user_requested())
.minimum_battery_percentage,
scheduler_stub()->trigger_conditions()->minimum_battery_percentage);
// Check that the observer got the notification that a page is available
EXPECT_TRUE(observer().added_called());
}
TEST_F(RequestCoordinatorTest, OfflinerDoneRequestSucceeded) {
// Add a request to the queue, wait for callbacks to finish.
offline_pages::SavePageRequest request(kRequestId1, kUrl1, kClientId1,
OfflineTimeNow(), kUserRequested);
SetupForOfflinerDoneCallbackTest(&request);
// Call the OfflinerDoneCallback to simulate the page being completed, wait
// for callbacks.
SendOfflinerDoneCallback(request, Offliner::RequestStatus::SAVED);
PumpLoop();
EXPECT_TRUE(processing_callback_called());
// Verify the request gets removed from the queue, and wait for callbacks.
queue()->GetRequests(base::BindOnce(&RequestCoordinatorTest::GetRequestsDone,
base::Unretained(this)));
PumpLoop();
// We should not find any requests in the queue anymore.
// RequestPicker should *not* have tried to start an additional job,
// because the request queue is empty now.
EXPECT_EQ(0UL, last_requests().size());
// Check that the observer got the notification that we succeeded, and that
// the request got removed from the queue.
EXPECT_TRUE(observer().completed_called());
histograms().ExpectBucketCount(
"OfflinePages.Background.FinalSavePageResult.bookmark", 0 /* SUCCESS */,
1);
EXPECT_EQ(RequestCoordinator::BackgroundSavePageResult::SUCCESS,
observer().last_status());
}
TEST_F(RequestCoordinatorTest, OfflinerDoneRequestSucceededButLostNetwork) {
// Add a request to the queue and set offliner done callback for it.
offline_pages::SavePageRequest request(kRequestId1, kUrl1, kClientId1,
OfflineTimeNow(), kUserRequested);
SetupForOfflinerDoneCallbackTest(&request);
EnableOfflinerCallback(false);
// Add a 2nd request to the queue.
AddRequest2();
// Disconnect network.
SetNetworkConnected(false);
// Call the OfflinerDoneCallback to simulate the page being completed, wait
// for callbacks.
SendOfflinerDoneCallback(request, Offliner::RequestStatus::SAVED);
PumpLoop();
EXPECT_TRUE(processing_callback_called());
// Verify not busy with 2nd request (since no connection).
EXPECT_FALSE(state() == RequestCoordinatorState::OFFLINING);
// Now connect network and verify processing starts.
SetNetworkConnected(true);
CallConnectionTypeObserver();
PumpLoop();
EXPECT_TRUE(state() == RequestCoordinatorState::OFFLINING);
}
TEST_F(RequestCoordinatorTest, OfflinerDoneRequestFailed) {
// Add a request to the queue, wait for callbacks to finish.
offline_pages::SavePageRequest request(kRequestId1, kUrl1, kClientId1,
OfflineTimeNow(), kUserRequested);
request.set_completed_attempt_count(kMaxCompletedTries - 1);
SetupForOfflinerDoneCallbackTest(&request);
// Stop processing before completing the second request on the queue.
EnableOfflinerCallback(false);
// Add second request to the queue to check handling when first fails.
AddRequest2();
PumpLoop();
// Call the OfflinerDoneCallback to simulate the request failed, wait
// for callbacks.
SendOfflinerDoneCallback(request, Offliner::RequestStatus::LOADING_FAILED);
PumpLoop();
// For retriable failure, processing should continue to 2nd request so
// no scheduler callback yet.
EXPECT_FALSE(processing_callback_called());
// Busy processing 2nd request.
EXPECT_TRUE(state() == RequestCoordinatorState::OFFLINING);
queue()->GetRequests(base::BindOnce(&RequestCoordinatorTest::GetRequestsDone,
base::Unretained(this)));
PumpLoop();
// Now just one request in the queue since failed request removed
// (max number of attempts exceeded).
EXPECT_EQ(1UL, last_requests().size());
// Check that the observer got the notification that we failed (and the
// subsequent notification that the request was removed) since we exceeded
// retry count.
EXPECT_TRUE(observer().completed_called());
histograms().ExpectBucketCount(
"OfflinePages.Background.FinalSavePageResult.bookmark",
6 /* RETRY_COUNT_EXCEEDED */, 1);
EXPECT_EQ(RequestCoordinator::BackgroundSavePageResult::RETRY_COUNT_EXCEEDED,
observer().last_status());
}
TEST_F(RequestCoordinatorTest, OfflinerDoneRequestFailedNoRetryFailure) {
// Add a request to the queue, wait for callbacks to finish.
offline_pages::SavePageRequest request(kRequestId1, kUrl1, kClientId1,
OfflineTimeNow(), kUserRequested);
SetupForOfflinerDoneCallbackTest(&request);
EnableOfflinerCallback(false);
// Add second request to the queue to check handling when first fails.
AddRequest2();
PumpLoop();
// Call the OfflinerDoneCallback to simulate the request failed, wait
// for callbacks.
SendOfflinerDoneCallback(request,
Offliner::RequestStatus::LOADING_FAILED_NO_RETRY);
PumpLoop();
// For no retry failure, processing should continue to 2nd request so
// no scheduler callback yet.
EXPECT_FALSE(processing_callback_called());
// Busy processing 2nd request.
EXPECT_TRUE(state() == RequestCoordinatorState::OFFLINING);
queue()->GetRequests(base::BindOnce(&RequestCoordinatorTest::GetRequestsDone,
base::Unretained(this)));
PumpLoop();
// Now just one request in the queue since non-retryable failure.
EXPECT_EQ(1UL, last_requests().size());
// Check that the observer got the notification that we failed (and the
// subsequent notification that the request was removed).
EXPECT_TRUE(observer().completed_called());
EXPECT_EQ(RequestCoordinator::BackgroundSavePageResult::LOADING_FAILURE,
observer().last_status());
// We should have a histogram entry for the effective network conditions
// when this failed request began.
histograms().ExpectBucketCount(
"OfflinePages.Background.EffectiveConnectionType.OffliningStartType."
"bookmark",
net::NetworkChangeNotifier::CONNECTION_UNKNOWN, 1);
}
TEST_F(RequestCoordinatorTest, OfflinerDoneRequestFailedNoNextFailure) {
// Add a request to the queue, wait for callbacks to finish.
offline_pages::SavePageRequest request(kRequestId1, kUrl1, kClientId1,
OfflineTimeNow(), kUserRequested);
SetupForOfflinerDoneCallbackTest(&request);
EnableOfflinerCallback(false);
// Add second request to the queue to check handling when first fails.
AddRequest2();
PumpLoop();
// Call the OfflinerDoneCallback to simulate the request failed, wait
// for callbacks.
SendOfflinerDoneCallback(request,
Offliner::RequestStatus::LOADING_FAILED_NO_NEXT);
PumpLoop();
// For no next failure, processing should not continue to 2nd request so
// expect scheduler callback.
EXPECT_TRUE(processing_callback_called());
// Not busy for NO_NEXT failure.
EXPECT_FALSE(state() == RequestCoordinatorState::OFFLINING);
queue()->GetRequests(base::BindOnce(&RequestCoordinatorTest::GetRequestsDone,
base::Unretained(this)));
PumpLoop();
// Both requests still in queue.
EXPECT_EQ(2UL, last_requests().size());
}
TEST_F(RequestCoordinatorTest, OfflinerDoneForegroundCancel) {
// Add a request to the queue, wait for callbacks to finish.
offline_pages::SavePageRequest request(kRequestId1, kUrl1, kClientId1,
OfflineTimeNow(), kUserRequested);
SetupForOfflinerDoneCallbackTest(&request);
// Call the OfflinerDoneCallback to simulate the request failed, wait
// for callbacks.
SendOfflinerDoneCallback(request,
Offliner::RequestStatus::FOREGROUND_CANCELED);
PumpLoop();
EXPECT_TRUE(processing_callback_called());
// Verify the request is not removed from the queue, and wait for callbacks.
queue()->GetRequests(base::BindOnce(&RequestCoordinatorTest::GetRequestsDone,
base::Unretained(this)));
PumpLoop();
// Request no longer in the queue (for single attempt policy).
EXPECT_EQ(1UL, last_requests().size());
// Verify foreground cancel not counted as an attempt after all.
EXPECT_EQ(0L, last_requests().at(0)->completed_attempt_count());
}
TEST_F(RequestCoordinatorTest, RequestDeferred) {
// Test handling of requests that can be deferred due to
// defer_while_page_is_active.
active_tab_info_->set_does_active_tab_match(true);
RequestCoordinator::SavePageLaterParams params;
params.url = kUrl1;
// Auto-async uses defer_background_fetch_while_page_is_active.
params.client_id = ClientId(kAutoAsyncNamespace, "1");
coordinator()->SavePageLater(params, base::DoNothing());
PumpLoop();
// Verify the request is not removed from the queue, and wait for callbacks.
queue()->GetRequests(base::BindOnce(&RequestCoordinatorTest::GetRequestsDone,
base::Unretained(this)));
PumpLoop();
// Request still in the queue.
ASSERT_EQ(1UL, last_requests().size());
EXPECT_EQ(1L, last_requests()[0]->started_attempt_count());
EXPECT_EQ(1L, last_requests()[0]->completed_attempt_count());
// The scheduler is called. Simulate the scheduler calling us back.
// This time, the request was tried recently, and will not be retried again.
// Since there are no requests this time, backup_schedule is called with a
// delay that matches the deferral interval.
ASSERT_TRUE(scheduler_stub()->schedule_called());
coordinator()->StartScheduledProcessing(device_conditions(),
processing_callback());
PumpLoop();
EXPECT_TRUE(scheduler_stub()->backup_schedule_called());
// Add plenty of tolerance to avoid flakes.
EXPECT_LT(PickRequestTask::kDeferInterval.InSeconds() - 10,
scheduler_stub()->schedule_delay());
}
TEST_F(RequestCoordinatorTest, RequestNotDeferred) {
// Test defer_while_page_is_active=true, but the DoesActiveTabMatch returns
// false. The page should be offlined.
active_tab_info_->set_does_active_tab_match(false);
RequestCoordinator::SavePageLaterParams params;
params.url = kUrl1;
// Auto-async uses defer_background_fetch_while_page_is_active.
params.client_id = ClientId(kAutoAsyncNamespace, "1");
coordinator()->SavePageLater(params, base::DoNothing());
PumpLoop();
queue()->GetRequests(base::BindOnce(&RequestCoordinatorTest::GetRequestsDone,
base::Unretained(this)));
PumpLoop();
// Request was completed.
ASSERT_EQ(0UL, last_requests().size());
}
// If one item completes, and there are no more user requeted items left,
// we should make a scheduler entry for a non-user requested item.
TEST_F(RequestCoordinatorTest, RequestNotPickedDisabledItemsRemain) {
coordinator()->StartScheduledProcessing(device_conditions(),
processing_callback());
EXPECT_TRUE(state() == RequestCoordinatorState::PICKING);
// Call RequestNotPicked, simulating a request on the disabled list.
CallRequestNotPicked(false, true);
PumpLoop();
EXPECT_FALSE(state() == RequestCoordinatorState::PICKING);
// The scheduler should have been called to schedule the disabled task for
// 5 minutes from now.
EXPECT_TRUE(scheduler_stub()->backup_schedule_called());
EXPECT_TRUE(scheduler_stub()->unschedule_called());
}
// If one item completes, and there are no more user requeted items left,
// we should make a scheduler entry for a non-user requested item.
TEST_F(RequestCoordinatorTest, RequestNotPickedNonUserRequestedItemsRemain) {
coordinator()->StartScheduledProcessing(device_conditions(),
processing_callback());
EXPECT_TRUE(state() == RequestCoordinatorState::PICKING);
// Call RequestNotPicked, and make sure we pick schedule a task for non user
// requested conditions, with no tasks on the disabled list.
CallRequestNotPicked(true, false);
PumpLoop();
EXPECT_FALSE(state() == RequestCoordinatorState::PICKING);
EXPECT_TRUE(processing_callback_called());
// The scheduler should have been called to schedule the non-user requested
// task.
EXPECT_TRUE(scheduler_stub()->schedule_called());
EXPECT_TRUE(scheduler_stub()->unschedule_called());
const Scheduler::TriggerConditions* conditions =
scheduler_stub()->trigger_conditions();
EXPECT_EQ(conditions->require_power_connected,
coordinator()->policy()->PowerRequired(!kUserRequested));
EXPECT_EQ(
conditions->minimum_battery_percentage,
coordinator()->policy()->BatteryPercentageRequired(!kUserRequested));
EXPECT_EQ(conditions->require_unmetered_network,
coordinator()->policy()->UnmeteredNetworkRequired(!kUserRequested));
}
TEST_F(RequestCoordinatorTest, SchedulerGetsLeastRestrictiveConditions) {
// Put two requests on the queue - The first is user requested, and
// the second is not user requested.
AddRequest1();
offline_pages::SavePageRequest request2(kRequestId2, kUrl2, kClientId2,
OfflineTimeNow(), !kUserRequested);
queue()->AddRequest(request2,
base::BindOnce(&RequestCoordinatorTest::AddRequestDone,
base::Unretained(this)));
PumpLoop();
// Trigger the scheduler to schedule for the least restrictive condition.
ScheduleForTest();
PumpLoop();
// Expect that the scheduler got notified, and it is at user_requested
// priority.
const Scheduler::TriggerConditions* conditions =
scheduler_stub()->trigger_conditions();
EXPECT_TRUE(scheduler_stub()->schedule_called());
EXPECT_EQ(conditions->require_power_connected,
coordinator()->policy()->PowerRequired(kUserRequested));
EXPECT_EQ(conditions->minimum_battery_percentage,
coordinator()->policy()->BatteryPercentageRequired(kUserRequested));
EXPECT_EQ(conditions->require_unmetered_network,
coordinator()->policy()->UnmeteredNetworkRequired(kUserRequested));
}
TEST_F(RequestCoordinatorTest, StartScheduledProcessingWithLoadingDisabled) {
// Add a request to the queue, wait for callbacks to finish.
AddRequest1();
PumpLoop();
DisableLoading();
EXPECT_TRUE(coordinator()->StartScheduledProcessing(device_conditions(),
processing_callback()));
// Let the async callbacks in the request coordinator run.
PumpLoop();
EXPECT_TRUE(processing_callback_called());
EXPECT_FALSE(state() == RequestCoordinatorState::PICKING);
EXPECT_EQ(Offliner::RequestStatus::LOADING_NOT_ACCEPTED,
last_offlining_status());
}
// TODO(dougarnett): Add StartScheduledProcessing test for QUEUE_UPDATE_FAILED.
// This tests a StopProcessing call before we have actually started the
// offliner.
TEST_F(RequestCoordinatorTest,
StartScheduledProcessingThenStopProcessingImmediately) {
// Add a request to the queue, wait for callbacks to finish.
AddRequest1();
PumpLoop();
EXPECT_TRUE(coordinator()->StartScheduledProcessing(device_conditions(),
processing_callback()));
EXPECT_TRUE(state() == RequestCoordinatorState::PICKING);
// Now, quick, before it can do much (we haven't called PumpLoop), cancel it
// with a status that precludes trying again.
StopProcessing(Offliner::RequestStatus::BACKGROUND_SCHEDULER_CANCELED);
// Let the async callbacks in the request coordinator run.
PumpLoop();
EXPECT_TRUE(processing_callback_called());
EXPECT_FALSE(state() == RequestCoordinatorState::PICKING);
// OfflinerDoneCallback will not end up getting called with status SAVED,
// since we cancelled the event before it called offliner_->LoadAndSave().
EXPECT_EQ(Offliner::RequestStatus::BACKGROUND_SCHEDULER_CANCELED,
last_offlining_status());
// Since offliner was not started, it will not have seen cancel call.
EXPECT_FALSE(OfflinerWasCanceled());
}
// This tests a StopProcessing call after the background loading has been
// started.
TEST_F(RequestCoordinatorTest,
StartScheduledProcessingThenStopProcessingLater) {
// Add a request to the queue, wait for callbacks to finish.
AddRequest1();
PumpLoop();
// Ensure the start processing request stops before the completion callback.
EnableOfflinerCallback(false);
EXPECT_TRUE(coordinator()->StartScheduledProcessing(device_conditions(),
processing_callback()));
EXPECT_TRUE(state() == RequestCoordinatorState::PICKING);
// Let all the async parts of the start processing pipeline run to completion.
PumpLoop();
// Observer called for starting processing.
EXPECT_TRUE(observer().changed_called());
EXPECT_EQ(SavePageRequest::RequestState::OFFLINING, observer().state());
observer().Clear();
// Since the offliner is disabled, this callback should not be called.
EXPECT_FALSE(processing_callback_called());
// Coordinator should now be busy.
EXPECT_TRUE(state() == RequestCoordinatorState::OFFLINING);
EXPECT_FALSE(state() == RequestCoordinatorState::PICKING);
// Now we cancel it while the background loader is busy.
StopProcessing(Offliner::RequestStatus::BACKGROUND_SCHEDULER_CANCELED);
// Let the async callbacks in the cancel run.
PumpLoop();
// Observer called for stopped processing.
EXPECT_TRUE(observer().changed_called());
EXPECT_EQ(SavePageRequest::RequestState::AVAILABLE, observer().state());
observer().Clear();
EXPECT_FALSE(state() == RequestCoordinatorState::OFFLINING);
// OfflinerDoneCallback will not end up getting called with status SAVED,
// since we cancelled the event before the LoadAndSave completed.
EXPECT_EQ(Offliner::RequestStatus::BACKGROUND_SCHEDULER_CANCELED,
last_offlining_status());
// Since offliner was started, it will have seen cancel call.
EXPECT_TRUE(OfflinerWasCanceled());
}
// Test handling of StartScheduledProcessing before a previous attempt has been
// fully stopped.
TEST_F(RequestCoordinatorTest, StartAttemptBeforeFullyStopped) {
// Add a request to the queue, wait for callbacks to finish.
AddRequest1();
PumpLoop();
// Ensure the start processing request stops before the completion callback.
EnableOfflinerCallback(false);
ASSERT_TRUE(coordinator()->StartScheduledProcessing(device_conditions(),
processing_callback()));
// Let all the async parts of the start processing pipeline run to completion.
PumpLoop();
// Observer called for starting processing.
ASSERT_TRUE(observer().changed_called());
ASSERT_EQ(SavePageRequest::RequestState::OFFLINING, observer().state());
observer().Clear();
// Now we cancel it while the background loader is busy, and attempt to start
// another request. Because the previous request hasn't completely stopped,
// the second request will not start.
StopProcessing(Offliner::RequestStatus::BACKGROUND_SCHEDULER_CANCELED);
AddRequest2();
EXPECT_FALSE(coordinator()->StartScheduledProcessing(device_conditions(),
processing_callback()));
PumpLoop();
EXPECT_FALSE(offliner()->has_pending_request());
}
// Verify the request coordinator can stop while in the picking state.
TEST_F(RequestCoordinatorTest, StopProcessingWhilePicking) {
// Add a request and start processing.
AddRequest1();
ASSERT_TRUE(coordinator()->StartScheduledProcessing(device_conditions(),
processing_callback()));
ASSERT_EQ(RequestCoordinatorState::PICKING, state());
StopProcessing(Offliner::RequestStatus::BACKGROUND_SCHEDULER_CANCELED);
// Let all the async parts of the start processing pipeline run to completion.
// The offliner should have never been called.
PumpLoop();
EXPECT_FALSE(OfflinerCalled());
}
// This tests that canceling a request will result in TryNextRequest() getting
// called.
TEST_F(RequestCoordinatorTest, RemoveInflightRequest) {
// Add a request to the queue, wait for callbacks to finish.
AddRequest1();
PumpLoop();
// Ensure the start processing request stops before the completion callback.
EnableOfflinerCallback(false);
EXPECT_TRUE(coordinator()->StartScheduledProcessing(device_conditions(),
processing_callback()));
// Let all the async parts of the start processing pipeline run to completion.
PumpLoop();
// Since the offliner is disabled, this callback should not be called.
EXPECT_FALSE(processing_callback_called());
// Remove the request while it is processing.
std::vector<int64_t> request_ids{kRequestId1};
coordinator()->RemoveRequests(
request_ids, base::BindOnce(&RequestCoordinatorTest::RemoveRequestsDone,
base::Unretained(this)));
// Let the async callbacks in the cancel run.
PumpLoop();
// Since offliner was started, it will have seen cancel call.
EXPECT_TRUE(OfflinerWasCanceled());
}
TEST_F(RequestCoordinatorTest, RemoveInflightRequestAndAddAnother) {
// Add a request to the queue, wait for callbacks to finish.
AddRequest1();
PumpLoop();
// Test a different ordering of tasks, by delaying the offliner cancellation.
offliner()->set_cancel_delay(base::TimeDelta::FromSeconds(1));
// Ensure the start processing request stops before the completion callback.
EnableOfflinerCallback(false);
EXPECT_TRUE(coordinator()->StartScheduledProcessing(device_conditions(),
processing_callback()));
// Let all the async parts of the start processing pipeline run to completion.
PumpLoop();
// Since the offliner is disabled, this callback should not be called.
EXPECT_FALSE(processing_callback_called());
// Add a new request, and remove current request while it is processing.
RequestCoordinator::SavePageLaterParams request2;
request2.url = kUrl2;
request2.client_id = kClientId2;
request2.user_requested = true;
coordinator()->SavePageLater(request2, base::DoNothing());
std::vector<int64_t> request_ids{kRequestId1};
coordinator()->RemoveRequests(
request_ids, base::BindOnce(&RequestCoordinatorTest::RemoveRequestsDone,
base::Unretained(this)));
AdvanceClockBy(base::TimeDelta::FromSeconds(2));
// Since offliner was started, it will have seen cancel call.
EXPECT_TRUE(OfflinerWasCanceled());
// The second request should be offlining now.
EXPECT_TRUE(offliner()->has_pending_request());
EXPECT_TRUE(state() == RequestCoordinatorState::OFFLINING);
}
TEST_F(RequestCoordinatorTest, MarkRequestCompleted) {
int64_t request_id = SavePageLaterWithAvailability(
RequestCoordinator::RequestAvailability::DISABLED_FOR_OFFLINER);
// Verify that the request is added to the disabled list.
EXPECT_FALSE(disabled_requests().empty());
PumpLoop();
EXPECT_NE(request_id, 0l);
// Verify request added in OFFLINING state.
EXPECT_TRUE(observer().added_called());
EXPECT_EQ(SavePageRequest::RequestState::OFFLINING, observer().state());
// Call the method under test, making sure we send SUCCESS to the observer.
coordinator()->MarkRequestCompleted(request_id);
PumpLoop();
// Our observer should have seen SUCCESS instead of USER_CANCELED.
EXPECT_EQ(RequestCoordinator::BackgroundSavePageResult::SUCCESS,
observer().last_status());
EXPECT_TRUE(observer().completed_called());
}
TEST_F(RequestCoordinatorTest, EnableForOffliner) {
// Pretend we are on low-end device so immediate start won't happen.
SetIsLowEndDeviceForTest(true);
int64_t request_id = SavePageLaterWithAvailability(
RequestCoordinator::RequestAvailability::DISABLED_FOR_OFFLINER);
// Verify that the request is added to the disabled list.
EXPECT_FALSE(disabled_requests().empty());
PumpLoop();
EXPECT_NE(request_id, 0l);
// Verify request added and initial change to OFFLINING (in foreground).
EXPECT_TRUE(observer().added_called());
EXPECT_TRUE(observer().changed_called());
EXPECT_EQ(SavePageRequest::RequestState::OFFLINING, observer().state());
observer().Clear();
// Ensure that the new request does not finish so we can verify state change.
EnableOfflinerCallback(false);
coordinator()->EnableForOffliner(request_id, kClientId1);
PumpLoop();
// Verify request changed again.
EXPECT_TRUE(observer().changed_called());
EXPECT_EQ(SavePageRequest::RequestState::AVAILABLE, observer().state());
}
TEST_F(RequestCoordinatorTest,
WatchdogTimeoutForScheduledProcessingNoLastSnapshot) {
// Build a request to use with the pre-renderer, and put it on the queue.
offline_pages::SavePageRequest request(kRequestId1, kUrl1, kClientId1,
OfflineTimeNow(), kUserRequested);
// Set request to allow one more completed attempt.
int max_tries = coordinator()->policy()->GetMaxCompletedTries();
request.set_completed_attempt_count(max_tries - 1);
queue()->AddRequest(request,
base::BindOnce(&RequestCoordinatorTest::AddRequestDone,
base::Unretained(this)));
PumpLoop();
// Ensure that the new request does not finish - we simulate it being
// in progress by asking it to skip making the completion callback.
EnableOfflinerCallback(false);
// Sending the request to the offliner.
EXPECT_TRUE(coordinator()->StartScheduledProcessing(device_conditions(),
waiting_callback()));
PumpLoop();
// Advance the mock clock far enough to cause a watchdog timeout
AdvanceClockBy(base::TimeDelta::FromSeconds(
coordinator()
->policy()
->GetSinglePageTimeLimitWhenBackgroundScheduledInSeconds() +
1));
PumpLoop();
// Wait for timeout to expire. Use a TaskRunner with a DelayedTaskRunner
// which won't time out immediately, so the watchdog thread doesn't kill valid
// tasks too soon.
WaitForCallback();
PumpLoop();
EXPECT_FALSE(state() == RequestCoordinatorState::PICKING);
EXPECT_FALSE(state() == RequestCoordinatorState::OFFLINING);
EXPECT_TRUE(OfflinerWasCanceled());
}
TEST_F(RequestCoordinatorTest,
WatchdogTimeoutForImmediateProcessingNoLastSnapshot) {
// Ensure that the new request does not finish - we simulate it being
// in progress by asking it to skip making the completion callback.
EnableOfflinerCallback(false);
EXPECT_NE(0, SavePageLater());
PumpLoop();
// Verify that immediate start from adding the request did happen.
EXPECT_TRUE(state() == RequestCoordinatorState::OFFLINING);
// Advance the mock clock 1 second before the watchdog timeout.
AdvanceClockBy(base::TimeDelta::FromSeconds(
coordinator()
->policy()
->GetSinglePageTimeLimitForImmediateLoadInSeconds() -
1));
PumpLoop();
// Verify still busy.
EXPECT_TRUE(state() == RequestCoordinatorState::OFFLINING);
EXPECT_FALSE(OfflinerWasCanceled());
// Advance the mock clock past the watchdog timeout now.
AdvanceClockBy(base::TimeDelta::FromSeconds(2));
PumpLoop();
// Verify the request timed out.
EXPECT_TRUE(OfflinerWasCanceled());
}
TEST_F(RequestCoordinatorTest, TimeBudgetExceeded) {
EnableOfflinerCallback(false);
// Build two requests to use with the pre-renderer, and put it on the queue.
AddRequest1();
// The second request will have a larger completed attempt count.
offline_pages::SavePageRequest request2(kRequestId1 + 1, kUrl1, kClientId1,
OfflineTimeNow(), kUserRequested);
request2.set_completed_attempt_count(kAttemptCount);
queue()->AddRequest(request2,
base::BindOnce(&RequestCoordinatorTest::AddRequestDone,
base::Unretained(this)));
PumpLoop();
// Sending the request to the offliner.
EXPECT_TRUE(coordinator()->StartScheduledProcessing(device_conditions(),
waiting_callback()));
PumpLoop();
// Advance the mock clock far enough to exceed our time budget.
// The first request will time out, and because we are over time budget,
// the second request will not be started.
AdvanceClockBy(base::TimeDelta::FromSeconds(kTestTimeBudgetSeconds));
PumpLoop();
// TryNextRequest should decide that there is no more work to be done,
// and call back to the scheduler, even though there is another request in the
// queue. Both requests should be left in the queue.
queue()->GetRequests(base::BindOnce(&RequestCoordinatorTest::GetRequestsDone,
base::Unretained(this)));
PumpLoop();
// We should find two requests in the queue.
// The first request should now have a completed count of 1.
EXPECT_EQ(2UL, last_requests().size());
EXPECT_EQ(1L, last_requests().at(0)->completed_attempt_count());
}
TEST_F(RequestCoordinatorTest, TryNextRequestWithNoNetwork) {
SavePageRequest request1 = AddRequest1();
AddRequest2();
PumpLoop();
// Set up for the call to StartScheduledProcessing.
EnableOfflinerCallback(false);
// Sending the request to the offliner.
EXPECT_TRUE(coordinator()->StartScheduledProcessing(device_conditions(),
waiting_callback()));
PumpLoop();
EXPECT_TRUE(state() == RequestCoordinatorState::OFFLINING);
// Now lose the network connection.
SetNetworkConnected(false);
// Complete first request and then TryNextRequest should decide not
// to pick another request (because of no network connection).
SendOfflinerDoneCallback(request1, Offliner::RequestStatus::SAVED);
PumpLoop();
// Not starting nor busy with next request.
EXPECT_FALSE(state() == RequestCoordinatorState::PICKING);
EXPECT_FALSE(state() == RequestCoordinatorState::OFFLINING);
// Get queued requests.
queue()->GetRequests(base::BindOnce(&RequestCoordinatorTest::GetRequestsDone,
base::Unretained(this)));
PumpLoop();
// We should find one request in the queue.
EXPECT_EQ(1UL, last_requests().size());
}
TEST_F(RequestCoordinatorTest, GetAllRequests) {
// Add two requests to the queue.
AddRequest1();
AddRequest2();
PumpLoop();
// Start the async status fetching.
coordinator()->GetAllRequests(base::BindOnce(
&RequestCoordinatorTest::GetQueuedRequestsDone, base::Unretained(this)));
PumpLoop();
// Wait for async get to finish.
WaitForCallback();
PumpLoop();
// Check that the statuses found in the callback match what we expect.
EXPECT_EQ(2UL, last_requests().size());
EXPECT_EQ(kRequestId1, last_requests().at(0)->request_id());
EXPECT_EQ(kRequestId2, last_requests().at(1)->request_id());
}
TEST_F(RequestCoordinatorTest, PauseAndResumeObserver) {
// Set low-end device status to actual status.
SetIsLowEndDeviceForTest(base::SysInfo::IsLowEndDevice());
// Add a request to the queue.
AddRequest1();
PumpLoop();
// Pause the request.
std::vector<int64_t> request_ids;
request_ids.push_back(kRequestId1);
coordinator()->PauseRequests(request_ids);
PumpLoop();
EXPECT_TRUE(observer().changed_called());
EXPECT_EQ(SavePageRequest::RequestState::PAUSED, observer().state());
// Clear out the observer before the next call.
observer().Clear();
// Resume the request.
coordinator()->ResumeRequests(request_ids);
PumpLoop();
EXPECT_TRUE(observer().changed_called());
// Now whether request is offlining or just available depends on whether test
// is run on svelte device or not.
if (base::SysInfo::IsLowEndDevice()) {
EXPECT_EQ(SavePageRequest::RequestState::AVAILABLE, observer().state());
} else {
EXPECT_EQ(SavePageRequest::RequestState::OFFLINING, observer().state());
}
}
TEST_F(RequestCoordinatorTest, RemoveRequest) {
// Add a request to the queue.
AddRequest1();
PumpLoop();
// Remove the request.
std::vector<int64_t> request_ids;
request_ids.push_back(kRequestId1);
coordinator()->RemoveRequests(
request_ids, base::BindOnce(&RequestCoordinatorTest::RemoveRequestsDone,
base::Unretained(this)));
PumpLoop();
WaitForCallback();
PumpLoop();
EXPECT_TRUE(observer().completed_called());
EXPECT_EQ(RequestCoordinator::BackgroundSavePageResult::USER_CANCELED,
observer().last_status());
EXPECT_EQ(1UL, last_remove_results().size());
EXPECT_EQ(kRequestId1, std::get<0>(last_remove_results().at(0)));
}
TEST_F(RequestCoordinatorTest,
SavePageStartsProcessingWhenConnectedAndNotLowEndDevice) {
// Turn off the callback so that the request stops before processing in
// PumpLoop.
EnableOfflinerCallback(false);
EXPECT_NE(0, SavePageLater());
PumpLoop();
EXPECT_TRUE(state() == RequestCoordinatorState::OFFLINING);
}
TEST_F(RequestCoordinatorTest,
SavePageStartsProcessingWhenConnectedOnLowEndDeviceIfFlagEnabled) {
// Mark device as low-end device.
SetIsLowEndDeviceForTest(true);
EXPECT_FALSE(offline_pages::IsOfflinePagesSvelteConcurrentLoadingEnabled());
// Make a request.
EXPECT_NE(0, SavePageLater());
PumpLoop();
// Verify not immediately busy (since low-end device).
EXPECT_FALSE(state() == RequestCoordinatorState::OFFLINING);
// Set feature flag to allow concurrent loads.
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeature(
kOfflinePagesSvelteConcurrentLoadingFeature);
EXPECT_TRUE(offline_pages::IsOfflinePagesSvelteConcurrentLoadingEnabled());
// Turn off the callback so that the request stops before processing in
// PumpLoop.
EnableOfflinerCallback(false);
// Make another request.
RequestCoordinator::SavePageLaterParams params;
params.url = kUrl2;
params.client_id = kClientId2;
params.user_requested = kUserRequested;
EXPECT_NE(0, coordinator()->SavePageLater(
params, base::BindOnce(
&RequestCoordinatorTest::SavePageRequestCallback,
base::Unretained(this))));
PumpLoop();
// Verify immediate processing did start this time.
EXPECT_TRUE(state() == RequestCoordinatorState::OFFLINING);
}
TEST_F(RequestCoordinatorTest, SavePageDoesntStartProcessingWhenDisconnected) {
SetNetworkConnected(false);
EnableOfflinerCallback(false);
EXPECT_NE(0, SavePageLater());
PumpLoop();
EXPECT_FALSE(state() == RequestCoordinatorState::OFFLINING);
// Now connect network and verify processing starts.
SetNetworkConnected(true);
CallConnectionTypeObserver();
PumpLoop();
EXPECT_TRUE(state() == RequestCoordinatorState::OFFLINING);
}
TEST_F(RequestCoordinatorTest,
SavePageDoesStartProcessingWhenPoorlyConnected) {
// Set specific network type for 2G with poor effective connection.
DeviceConditions device_conditions(!kPowerRequired, kBatteryPercentageHigh,
net::NetworkChangeNotifier::CONNECTION_2G);
SetDeviceConditionsForTest(device_conditions);
SetEffectiveConnectionTypeForTest(
net::EffectiveConnectionType::EFFECTIVE_CONNECTION_TYPE_SLOW_2G);
// Turn off the callback so that the request stops before processing in
// PumpLoop.
EnableOfflinerCallback(false);
EXPECT_NE(0, SavePageLater());
PumpLoop();
EXPECT_TRUE(state() == RequestCoordinatorState::OFFLINING);
}
TEST_F(RequestCoordinatorTest,
ResumeStartsProcessingWhenConnectedAndNotLowEndDevice) {
// Start unconnected.
SetNetworkConnected(false);
// Turn off the callback so that the request stops before processing in
// PumpLoop.
EnableOfflinerCallback(false);
// Add a request to the queue.
AddRequest1();
PumpLoop();
EXPECT_FALSE(state() == RequestCoordinatorState::OFFLINING);
// Pause the request.
std::vector<int64_t> request_ids;
request_ids.push_back(kRequestId1);
coordinator()->PauseRequests(request_ids);
PumpLoop();
// Resume the request while disconnected.
coordinator()->ResumeRequests(request_ids);
PumpLoop();
EXPECT_FALSE(state() == RequestCoordinatorState::OFFLINING);
EXPECT_EQ(1UL, prioritized_requests().size());
// Pause the request again.
coordinator()->PauseRequests(request_ids);
PumpLoop();
EXPECT_EQ(0UL, prioritized_requests().size());
// Now simulate reasonable connection.
SetNetworkConnected(true);
// Resume the request while connected.
coordinator()->ResumeRequests(request_ids);
EXPECT_FALSE(state() == RequestCoordinatorState::OFFLINING);
PumpLoop();
EXPECT_EQ(1UL, prioritized_requests().size());
EXPECT_TRUE(state() == RequestCoordinatorState::OFFLINING);
}
TEST_F(RequestCoordinatorTest, SnapshotOnLastTryForScheduledProcessing) {
// Build a request to use with the pre-renderer, and put it on the queue.
offline_pages::SavePageRequest request(kRequestId1, kUrl1, kClientId1,
OfflineTimeNow(), kUserRequested);
// Set request to allow one more completed attempt. So that the next try would
// be the last retry.
int max_tries = coordinator()->policy()->GetMaxCompletedTries();
request.set_completed_attempt_count(max_tries - 1);
queue()->AddRequest(request,
base::BindOnce(&RequestCoordinatorTest::AddRequestDone,
base::Unretained(this)));
PumpLoop();
// Ensure that the new request does not finish - we simulate it being
// in progress by asking it to skip making the completion callback.
// Also make snapshot on last retry happen in this case.
EnableOfflinerCallback(false);
EnableSnapshotOnLastRetry();
// Sending the request to the offliner.
EXPECT_TRUE(coordinator()->StartScheduledProcessing(device_conditions(),
waiting_callback()));
PumpLoop();
// Advance the mock clock far enough to cause a watchdog timeout
AdvanceClockBy(base::TimeDelta::FromSeconds(
coordinator()
->policy()
->GetSinglePageTimeLimitWhenBackgroundScheduledInSeconds() +
1));
PumpLoop();
// Wait for timeout to expire. Use a TaskRunner with a DelayedTaskRunner
// which won't time out immediately, so the watchdog thread doesn't kill valid
// tasks too soon.
WaitForCallback();
PumpLoop();
// Check the offliner didn't get a cancel and the result was success.
EXPECT_FALSE(OfflinerWasCanceled());
EXPECT_EQ(RequestCoordinator::BackgroundSavePageResult::SUCCESS,
observer().last_status());
EXPECT_TRUE(observer().completed_called());
}
TEST_F(RequestCoordinatorTest, SnapshotOnLastTryForImmediateProcessing) {
// Ensure that the new request does not finish - we simulate it being
// in progress by asking it to skip making the completion callback.
EnableOfflinerCallback(false);
EXPECT_NE(0, SavePageLater());
// Repeat the timeout for MaxCompleteTries - 1 times in order to increase the
// completed tries on this request.
int max_tries = coordinator()->policy()->GetMaxCompletedTries();
for (int i = 0; i < max_tries - 1; i++) {
PumpLoop();
// Reset states.
ResetOfflinerWasCanceled();
observer().Clear();
// Verify that the request is being processed.
EXPECT_TRUE(state() == RequestCoordinatorState::OFFLINING);
// Advance the mock clock 1 second more than the watchdog timeout.
AdvanceClockBy(base::TimeDelta::FromSeconds(
coordinator()
->policy()
->GetSinglePageTimeLimitForImmediateLoadInSeconds() +
1));
PumpLoop();
// Verify the request timed out.
EXPECT_TRUE(OfflinerWasCanceled());
EXPECT_TRUE(observer().changed_called());
}
// Reset states.
ResetOfflinerWasCanceled();
observer().Clear();
// Make snapshot on last retry happen.
EnableSnapshotOnLastRetry();
// Advance the mock clock 1 second more than the watchdog timeout.
AdvanceClockBy(base::TimeDelta::FromSeconds(
coordinator()
->policy()
->GetSinglePageTimeLimitForImmediateLoadInSeconds() +
1));
PumpLoop();
// The last time would trigger the snapshot on last retry and succeed.
EXPECT_FALSE(OfflinerWasCanceled());
EXPECT_EQ(RequestCoordinator::BackgroundSavePageResult::SUCCESS,
observer().last_status());
EXPECT_TRUE(observer().completed_called());
}
TEST_F(RequestCoordinatorTest, RequestPendingNetworkOnResumeWithNoNetwork) {
// Add a request to the queue.
AddRequest1();
PumpLoop();
// Pause the request.
std::vector<int64_t> request_ids;
request_ids.push_back(kRequestId1);
coordinator()->PauseRequests(request_ids);
PumpLoop();
SetNetworkConnected(false);
// Resume the request.
coordinator()->ResumeRequests(request_ids);
PumpLoop();
// Verify the request is waiting for network.
EXPECT_TRUE(observer().changed_called());
EXPECT_EQ(SavePageRequest::RequestState::AVAILABLE, observer().state());
EXPECT_EQ(PendingState::PENDING_NETWORK, observer().pending_state());
}
TEST_F(RequestCoordinatorTest,
RequestPendingDownloadOnResumeWithOffliningDownload) {
// Add a request to the queue.
AddRequest1();
PumpLoop();
// Pause the request.
std::vector<int64_t> request_ids;
request_ids.push_back(kRequestId1);
coordinator()->PauseRequests(request_ids);
PumpLoop();
// Add a second request to the queue for offlining.
AddRequest2();
EnableOfflinerCallback(false);
EXPECT_TRUE(coordinator()->StartScheduledProcessing(device_conditions(),
processing_callback()));
PumpLoop();
// Resume the first request.
coordinator()->ResumeRequests(request_ids);
PumpLoop();
// Verify the request is waiting for another download to complete.
EXPECT_TRUE(observer().changed_called());
EXPECT_EQ(SavePageRequest::RequestState::AVAILABLE, observer().state());
EXPECT_EQ(PendingState::PENDING_ANOTHER_DOWNLOAD, observer().pending_state());
}
TEST_F(RequestCoordinatorTest, RequestPendingNetworkOnAddWithNoNetwork) {
SetNetworkConnected(false);
EXPECT_NE(0, SavePageLater());
PumpLoop();
// Verify the request is waiting for network.
EXPECT_EQ(SavePageRequest::RequestState::AVAILABLE, observer().state());
EXPECT_EQ(PendingState::PENDING_NETWORK, observer().pending_state());
}
TEST_F(RequestCoordinatorTest,
RequestPendingDownloadOnAddedWithOffliningDownload) {
// Make an offlining request.
EnableOfflinerCallback(false);
EXPECT_NE(0, SavePageLater());
PumpLoop();
// Make a second request.
RequestCoordinator::SavePageLaterParams params;
params.url = kUrl2;
params.client_id = kClientId2;
params.user_requested = kUserRequested;
EXPECT_NE(0, coordinator()->SavePageLater(
params, base::BindOnce(
&RequestCoordinatorTest::SavePageRequestCallback,
base::Unretained(this))));
PumpLoop();
// Verify second request is waiting for another download to complete.
EXPECT_TRUE(observer().added_called());
EXPECT_EQ(SavePageRequest::RequestState::AVAILABLE, observer().state());
EXPECT_EQ(PendingState::PENDING_ANOTHER_DOWNLOAD, observer().pending_state());
}
} // namespace offline_pages