blob: c91909c80abcc34c0b14921c5293a677312ebc37 [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/background/request_coordinator.h"
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/synchronization/waitable_event.h"
#include "base/sys_info.h"
#include "base/test/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_pages/background/device_conditions.h"
#include "components/offline_pages/background/network_quality_provider_stub.h"
#include "components/offline_pages/background/offliner.h"
#include "components/offline_pages/background/offliner_factory.h"
#include "components/offline_pages/background/offliner_factory_stub.h"
#include "components/offline_pages/background/offliner_policy.h"
#include "components/offline_pages/background/offliner_stub.h"
#include "components/offline_pages/background/pick_request_task_factory.h"
#include "components/offline_pages/background/request_queue.h"
#include "components/offline_pages/background/request_queue_in_memory_store.h"
#include "components/offline_pages/background/save_page_request.h"
#include "components/offline_pages/background/scheduler.h"
#include "components/offline_pages/background/scheduler_stub.h"
#include "components/offline_pages/offline_page_feature.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;
} // namespace
class ObserverStub : public RequestCoordinator::Observer {
public:
ObserverStub()
: added_called_(false),
completed_called_(false),
changed_called_(false),
last_status_(RequestCoordinator::BackgroundSavePageResult::SUCCESS),
state_(SavePageRequest::RequestState::OFFLINING) {}
void Clear() {
added_called_ = false;
completed_called_ = false;
changed_called_ = false;
state_ = SavePageRequest::RequestState::OFFLINING;
last_status_ = RequestCoordinator::BackgroundSavePageResult::SUCCESS;
}
void OnAdded(const SavePageRequest& request) override {
added_called_ = true;
}
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();
}
bool added_called() { return added_called_; }
bool completed_called() { return completed_called_; }
bool changed_called() { return changed_called_; }
RequestCoordinator::BackgroundSavePageResult last_status() {
return last_status_;
}
SavePageRequest::RequestState state() { return state_; }
private:
bool added_called_;
bool completed_called_;
bool changed_called_;
RequestCoordinator::BackgroundSavePageResult last_status_;
SavePageRequest::RequestState state_;
};
class RequestCoordinatorTest
: public testing::Test {
public:
RequestCoordinatorTest();
~RequestCoordinatorTest() override;
void SetUp() override;
void PumpLoop();
RequestCoordinator* coordinator() {
return coordinator_.get();
}
bool is_busy() {
return coordinator_->is_busy();
}
bool is_starting() { return coordinator_->is_starting(); }
// Empty callback function.
void ImmediateScheduleCallbackFunction(bool result) {
immediate_schedule_callback_called_ = true;
immediate_schedule_callback_result_ = result;
}
// Callback function which releases a wait for it.
void WaitingCallbackFunction(bool result) {
waiter_.Signal();
}
net::NetworkChangeNotifier::ConnectionType GetConnectionType() {
return coordinator()->GetConnectionType();
}
// 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 SetNetworkConditionsForTest(
net::NetworkChangeNotifier::ConnectionType connection) {
coordinator()->SetNetworkConditionsForTest(connection);
}
void SetEffectiveConnectionTypeForTest(net::EffectiveConnectionType type) {
network_quality_estimator_->SetEffectiveConnectionTypeForTest(type);
}
void SetNetworkConnected(bool connected) {
if (connected) {
SetNetworkConditionsForTest(
net::NetworkChangeNotifier::ConnectionType::CONNECTION_3G);
SetEffectiveConnectionTypeForTest(
net::EffectiveConnectionType::EFFECTIVE_CONNECTION_TYPE_3G);
} else {
SetNetworkConditionsForTest(
net::NetworkChangeNotifier::ConnectionType::CONNECTION_NONE);
SetEffectiveConnectionTypeForTest(
net::EffectiveConnectionType::EFFECTIVE_CONNECTION_TYPE_OFFLINE);
}
}
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);
}
void SetDeviceConditionsForTest(DeviceConditions device_conditions) {
coordinator_->SetDeviceConditionsForTest(device_conditions);
}
void WaitForCallback() {
waiter_.Wait();
}
void AdvanceClockBy(base::TimeDelta delta) {
task_runner_->FastForwardBy(delta);
}
SavePageRequest AddRequest1();
SavePageRequest AddRequest2();
Offliner::RequestStatus last_offlining_status() const {
return coordinator_->last_offlining_status_;
}
bool OfflinerWasCanceled() const { return offliner_->cancel_called(); }
ObserverStub observer() { return observer_; }
DeviceConditions device_conditions() { return device_conditions_; }
base::Callback<void(bool)> immediate_callback() {
return immediate_callback_;
}
base::Callback<void(bool)> waiting_callback() { return waiting_callback_; }
bool immediate_schedule_callback_called() const {
return immediate_schedule_callback_called_;
}
bool immediate_schedule_callback_result() const {
return immediate_schedule_callback_result_;
}
const base::HistogramTester& histograms() const { return histogram_tester_; }
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_;
std::unique_ptr<NetworkQualityProviderStub> network_quality_estimator_;
std::unique_ptr<RequestCoordinator> coordinator_;
OfflinerStub* offliner_;
base::WaitableEvent waiter_;
ObserverStub observer_;
bool immediate_schedule_callback_called_;
bool immediate_schedule_callback_result_;
DeviceConditions device_conditions_;
base::Callback<void(bool)> immediate_callback_;
base::Callback<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),
immediate_schedule_callback_called_(false),
immediate_schedule_callback_result_(false),
device_conditions_(!kPowerRequired,
kBatteryPercentageHigh,
net::NetworkChangeNotifier::CONNECTION_3G) {}
RequestCoordinatorTest::~RequestCoordinatorTest() {}
void RequestCoordinatorTest::SetUp() {
std::unique_ptr<OfflinerPolicy> policy(new OfflinerPolicy());
std::unique_ptr<OfflinerFactory> offliner_factory(new OfflinerFactoryStub());
// Save the offliner for use by the tests.
offliner_ = reinterpret_cast<OfflinerStub*>(
offliner_factory->GetOffliner(policy.get()));
std::unique_ptr<RequestQueueInMemoryStore>
store(new RequestQueueInMemoryStore());
std::unique_ptr<RequestQueue> queue(new RequestQueue(std::move(store)));
std::unique_ptr<Scheduler> scheduler_stub(new SchedulerStub());
network_quality_estimator_.reset(new NetworkQualityProviderStub());
coordinator_.reset(new RequestCoordinator(
std::move(policy), std::move(offliner_factory), std::move(queue),
std::move(scheduler_stub), network_quality_estimator_.get()));
coordinator_->AddObserver(&observer_);
SetNetworkConnected(true);
std::unique_ptr<PickRequestTaskFactory> picker_factory(
new PickRequestTaskFactory(
coordinator_->policy(),
static_cast<RequestNotifier*>(coordinator_.get()),
coordinator_->GetLogger()));
coordinator_->queue()->SetPickerFactory(std::move(picker_factory));
immediate_callback_ =
base::Bind(&RequestCoordinatorTest::ImmediateScheduleCallbackFunction,
base::Unretained(this));
// Override the normal immediate callback with a wait releasing callback.
waiting_callback_ = base::Bind(
&RequestCoordinatorTest::WaitingCallbackFunction, base::Unretained(this));
SetDeviceConditionsForTest(device_conditions_);
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(base::Time::Now());
coordinator()->queue()->AddRequest(
*request, base::Bind(&RequestCoordinatorTest::AddRequestDone,
base::Unretained(this)));
PumpLoop();
// Override the processing callback for test visiblity.
base::Callback<void(bool)> callback =
base::Bind(&RequestCoordinatorTest::ImmediateScheduleCallbackFunction,
base::Unretained(this));
coordinator()->SetProcessingCallbackForTest(callback);
// Mock that coordinator is in actively processing state starting now.
SetProcessingStateForTest(
RequestCoordinator::ProcessingWindowState::IMMEDIATE_WINDOW);
SetOperationStartTimeForTest(base::Time::Now());
}
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,
base::Time::Now(), kUserRequested);
coordinator()->queue()->AddRequest(
request1, base::Bind(&RequestCoordinatorTest::AddRequestDone,
base::Unretained(this)));
return request1;
}
SavePageRequest RequestCoordinatorTest::AddRequest2() {
offline_pages::SavePageRequest request2(kRequestId2, kUrl2, kClientId2,
base::Time::Now(), kUserRequested);
coordinator()->queue()->AddRequest(
request2, base::Bind(&RequestCoordinatorTest::AddRequestDone,
base::Unretained(this)));
return request2;
}
TEST_F(RequestCoordinatorTest, StartProcessingWithNoRequests) {
EXPECT_TRUE(coordinator()->StartProcessing(device_conditions(),
immediate_callback()));
PumpLoop();
EXPECT_TRUE(immediate_schedule_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, StartProcessingWithRequestInProgress) {
// Start processing for this request.
EXPECT_NE(
coordinator()->SavePageLater(
kUrl1, kClientId1, kUserRequested,
RequestCoordinator::RequestAvailability::ENABLED_FOR_OFFLINER), 0);
// 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()->StartProcessing(device_conditions(),
immediate_callback()));
PumpLoop();
EXPECT_TRUE(is_busy());
// Since the offliner is disabled, this callback should not be called.
EXPECT_FALSE(immediate_schedule_callback_called());
// Now trying to start processing on another request should return false.
EXPECT_FALSE(coordinator()->StartProcessing(device_conditions(),
immediate_callback()));
}
TEST_F(RequestCoordinatorTest, SavePageLater) {
// The user-requested request which gets processed by SavePageLater
// would invoke user request callback.
coordinator()->SetImmediateScheduleCallbackForTest(immediate_callback());
EXPECT_NE(
coordinator()->SavePageLater(
kUrl1, kClientId1, kUserRequested,
RequestCoordinator::RequestAvailability::ENABLED_FOR_OFFLINER), 0);
// Expect that a request got placed on the queue.
coordinator()->queue()->GetRequests(base::Bind(
&RequestCoordinatorTest::GetRequestsDone, base::Unretained(this)));
// Wait for callbacks to finish, both request queue and offliner.
PumpLoop();
EXPECT_TRUE(immediate_schedule_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.
SchedulerStub* scheduler_stub =
reinterpret_cast<SchedulerStub*>(coordinator()->scheduler());
EXPECT_TRUE(scheduler_stub->schedule_called());
EXPECT_EQ(coordinator()
->GetTriggerConditions(last_requests()[0]->user_requested())
.minimum_battery_percentage,
scheduler_stub->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.
if (base::SysInfo::IsLowEndDevice()) {
histograms().ExpectBucketCount(
"OfflinePages.Background.ImmediateStart.AvailableRequestCount.Svelte",
1, 1);
} else {
histograms().ExpectBucketCount(
"OfflinePages.Background.ImmediateStart.AvailableRequestCount", 1, 1);
}
}
TEST_F(RequestCoordinatorTest, SavePageLaterFailed) {
// The user-requested request which gets processed by SavePageLater
// would invoke user request callback.
coordinator()->SetImmediateScheduleCallbackForTest(immediate_callback());
EXPECT_TRUE(
coordinator()->SavePageLater(
kUrl1, kClientId1, kUserRequested,
RequestCoordinator::RequestAvailability::ENABLED_FOR_OFFLINER) != 0);
// Expect that a request got placed on the queue.
coordinator()->queue()->GetRequests(
base::Bind(&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(immediate_schedule_callback_called());
if (base::SysInfo::IsLowEndDevice()) {
EXPECT_FALSE(immediate_schedule_callback_result());
} else {
EXPECT_TRUE(immediate_schedule_callback_result());
}
// 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.
SchedulerStub* scheduler_stub = reinterpret_cast<SchedulerStub*>(
coordinator()->scheduler());
EXPECT_TRUE(scheduler_stub->schedule_called());
EXPECT_EQ(coordinator()
->GetTriggerConditions(last_requests()[0]->user_requested())
.minimum_battery_percentage,
scheduler_stub->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, base::Time::Now(), kUserRequested);
SetupForOfflinerDoneCallbackTest(&request);
// Call the OfflinerDoneCallback to simulate the page being completed, wait
// for callbacks.
SendOfflinerDoneCallback(request, Offliner::RequestStatus::SAVED);
PumpLoop();
EXPECT_TRUE(immediate_schedule_callback_called());
// Verify the request gets removed from the queue, and wait for callbacks.
coordinator()->queue()->GetRequests(
base::Bind(&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());
EXPECT_EQ(RequestCoordinator::BackgroundSavePageResult::SUCCESS,
observer().last_status());
}
TEST_F(RequestCoordinatorTest, OfflinerDoneRequestFailed) {
// Add a request to the queue, wait for callbacks to finish.
offline_pages::SavePageRequest request(
kRequestId1, kUrl1, kClientId1, base::Time::Now(), 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::PRERENDERING_FAILED);
PumpLoop();
// For retriable failure, processing should stop and scheduler callback
// called (so that request can be retried first next processing window).
EXPECT_TRUE(immediate_schedule_callback_called());
// TODO(dougarnett): Consider injecting mock RequestPicker for this test
// and verifying that there is no attempt to pick another request following
// this failure code.
coordinator()->queue()->GetRequests(
base::Bind(&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).
EXPECT_TRUE(observer().completed_called());
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,
base::Time::Now(), 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::PRERENDERING_FAILED_NO_RETRY);
PumpLoop();
// For no retry failure, processing should continue to 2nd request so
// no scheduler callback yet.
EXPECT_FALSE(immediate_schedule_callback_called());
// TODO(dougarnett): Consider injecting mock RequestPicker for this test
// and verifying that there is as attempt to pick another request following
// this non-retryable failure code.
coordinator()->queue()->GetRequests(base::Bind(
&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::PRERENDER_FAILURE,
observer().last_status());
}
TEST_F(RequestCoordinatorTest, OfflinerDoneForegroundCancel) {
// Add a request to the queue, wait for callbacks to finish.
offline_pages::SavePageRequest request(
kRequestId1, kUrl1, kClientId1, base::Time::Now(), kUserRequested);
SetupForOfflinerDoneCallbackTest(&request);
// Call the OfflinerDoneCallback to simulate the request failed, wait
// for callbacks.
SendOfflinerDoneCallback(request,
Offliner::RequestStatus::FOREGROUND_CANCELED);
PumpLoop();
EXPECT_TRUE(immediate_schedule_callback_called());
// Verify the request is not removed from the queue, and wait for callbacks.
coordinator()->queue()->GetRequests(base::Bind(
&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, OfflinerDonePrerenderingCancel) {
// Add a request to the queue, wait for callbacks to finish.
offline_pages::SavePageRequest request(kRequestId1, kUrl1, kClientId1,
base::Time::Now(), kUserRequested);
SetupForOfflinerDoneCallbackTest(&request);
// Call the OfflinerDoneCallback to simulate the request failed, wait
// for callbacks.
SendOfflinerDoneCallback(request,
Offliner::RequestStatus::PRERENDERING_CANCELED);
PumpLoop();
EXPECT_TRUE(immediate_schedule_callback_called());
// Verify the request is not removed from the queue, and wait for callbacks.
coordinator()->queue()->GetRequests(base::Bind(
&RequestCoordinatorTest::GetRequestsDone, base::Unretained(this)));
PumpLoop();
// Request still in the queue.
EXPECT_EQ(1UL, last_requests().size());
// Verify prerendering cancel not counted as an attempt after all.
const std::unique_ptr<SavePageRequest>& found_request =
last_requests().front();
EXPECT_EQ(0L, found_request->completed_attempt_count());
}
// 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()->StartProcessing(device_conditions(), immediate_callback());
EXPECT_TRUE(is_starting());
// Call RequestNotPicked, simulating a request on the disabled list.
CallRequestNotPicked(false, true);
PumpLoop();
EXPECT_FALSE(is_starting());
// The scheduler should have been called to schedule the disabled task for
// 5 minutes from now.
SchedulerStub* scheduler_stub =
reinterpret_cast<SchedulerStub*>(coordinator()->scheduler());
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()->StartProcessing(device_conditions(), immediate_callback());
EXPECT_TRUE(is_starting());
// 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(is_starting());
EXPECT_TRUE(immediate_schedule_callback_called());
// The scheduler should have been called to schedule the non-user requested
// task.
SchedulerStub* scheduler_stub =
reinterpret_cast<SchedulerStub*>(coordinator()->scheduler());
EXPECT_TRUE(scheduler_stub->schedule_called());
EXPECT_TRUE(scheduler_stub->unschedule_called());
const Scheduler::TriggerConditions* conditions = scheduler_stub->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,
base::Time::Now(), !kUserRequested);
coordinator()->queue()->AddRequest(
request2, base::Bind(&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.
SchedulerStub* scheduler_stub =
reinterpret_cast<SchedulerStub*>(coordinator()->scheduler());
const Scheduler::TriggerConditions* conditions = scheduler_stub->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, StartProcessingWithLoadingDisabled) {
// Add a request to the queue, wait for callbacks to finish.
AddRequest1();
PumpLoop();
DisableLoading();
EXPECT_TRUE(coordinator()->StartProcessing(device_conditions(),
immediate_callback()));
// Let the async callbacks in the request coordinator run.
PumpLoop();
EXPECT_TRUE(immediate_schedule_callback_called());
EXPECT_FALSE(is_starting());
EXPECT_EQ(Offliner::PRERENDERING_NOT_STARTED, last_offlining_status());
}
// This tests a StopProcessing call before we have actually started the
// prerenderer.
TEST_F(RequestCoordinatorTest, StartProcessingThenStopProcessingImmediately) {
// Add a request to the queue, wait for callbacks to finish.
AddRequest1();
PumpLoop();
EXPECT_TRUE(coordinator()->StartProcessing(device_conditions(),
immediate_callback()));
EXPECT_TRUE(is_starting());
// Now, quick, before it can do much (we haven't called PumpLoop), cancel it.
coordinator()->StopProcessing(Offliner::REQUEST_COORDINATOR_CANCELED);
// Let the async callbacks in the request coordinator run.
PumpLoop();
EXPECT_TRUE(immediate_schedule_callback_called());
EXPECT_FALSE(is_starting());
// OfflinerDoneCallback will not end up getting called with status SAVED,
// since we cancelled the event before it called offliner_->LoadAndSave().
EXPECT_EQ(Offliner::RequestStatus::REQUEST_COORDINATOR_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 prerenderer has been started.
TEST_F(RequestCoordinatorTest, StartProcessingThenStopProcessingLater) {
// 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()->StartProcessing(device_conditions(),
immediate_callback()));
EXPECT_TRUE(is_starting());
// 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(immediate_schedule_callback_called());
// Coordinator should now be busy.
EXPECT_TRUE(is_busy());
EXPECT_FALSE(is_starting());
// Now we cancel it while the prerenderer is busy.
coordinator()->StopProcessing(Offliner::REQUEST_COORDINATOR_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(is_busy());
// OfflinerDoneCallback will not end up getting called with status SAVED,
// since we cancelled the event before the LoadAndSave completed.
EXPECT_EQ(Offliner::RequestStatus::REQUEST_COORDINATOR_CANCELED,
last_offlining_status());
// Since offliner was started, it will have seen cancel call.
EXPECT_TRUE(OfflinerWasCanceled());
}
// 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()->StartProcessing(device_conditions(),
immediate_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(immediate_schedule_callback_called());
// Remove the request while it is processing.
std::vector<int64_t> request_ids{kRequestId1};
coordinator()->RemoveRequests(
request_ids, base::Bind(&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, MarkRequestCompleted) {
int64_t request_id = coordinator()->SavePageLater(
kUrl1, kClientId1, kUserRequested,
RequestCoordinator::RequestAvailability::DISABLED_FOR_OFFLINER);
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 REMOVED.
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 = coordinator()->SavePageLater(
kUrl1, kClientId1, kUserRequested,
RequestCoordinator::RequestAvailability::DISABLED_FOR_OFFLINER);
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, WatchdogTimeoutForScheduledProcessing) {
// Build a request to use with the pre-renderer, and put it on the queue.
offline_pages::SavePageRequest request(
kRequestId1, kUrl1, kClientId1, base::Time::Now(), kUserRequested);
// Set request to allow one more completed attempt.
int max_tries = coordinator()->policy()->GetMaxCompletedTries();
request.set_completed_attempt_count(max_tries - 1);
coordinator()->queue()->AddRequest(
request,
base::Bind(&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()->StartProcessing(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(is_starting());
EXPECT_FALSE(coordinator()->is_busy());
EXPECT_TRUE(OfflinerWasCanceled());
}
TEST_F(RequestCoordinatorTest, WatchdogTimeoutForImmediateProcessing) {
// If low end device, pretend it is not so that immediate start happens.
SetIsLowEndDeviceForTest(false);
// 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(
coordinator()->SavePageLater(
kUrl1, kClientId1, kUserRequested,
RequestCoordinator::RequestAvailability::ENABLED_FOR_OFFLINER), 0);
PumpLoop();
// Verify that immediate start from adding the request did happen.
EXPECT_TRUE(coordinator()->is_busy());
// Advance the mock clock 1 second before the watchdog timeout.
AdvanceClockBy(base::TimeDelta::FromSeconds(
coordinator()
->policy()
->GetSinglePageTimeLimitForImmediateLoadInSeconds() - 1));
PumpLoop();
// Verify still busy.
EXPECT_TRUE(coordinator()->is_busy());
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,
base::Time::Now(), kUserRequested);
request2.set_completed_attempt_count(kAttemptCount);
coordinator()->queue()->AddRequest(
request2, base::Bind(&RequestCoordinatorTest::AddRequestDone,
base::Unretained(this)));
PumpLoop();
// Sending the request to the offliner.
EXPECT_TRUE(
coordinator()->StartProcessing(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.
coordinator()->queue()->GetRequests(
base::Bind(&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 StartProcessing.
EnableOfflinerCallback(false);
// Sending the request to the offliner.
EXPECT_TRUE(
coordinator()->StartProcessing(device_conditions(), waiting_callback()));
PumpLoop();
EXPECT_TRUE(coordinator()->is_busy());
// 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(coordinator()->is_starting());
EXPECT_FALSE(coordinator()->is_busy());
// Get queued requests.
coordinator()->queue()->GetRequests(base::Bind(
&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::Bind(
&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());
}
#if defined(OS_IOS)
// Flaky on IOS. http://crbug/663311
#define MAYBE_PauseAndResumeObserver DISABLED_PauseAndResumeObserver
#else
#define MAYBE_PauseAndResumeObserver PauseAndResumeObserver
#endif
TEST_F(RequestCoordinatorTest, MAYBE_PauseAndResumeObserver) {
// 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::Bind(&RequestCoordinatorTest::RemoveRequestsDone,
base::Unretained(this)));
PumpLoop();
WaitForCallback();
PumpLoop();
EXPECT_TRUE(observer().completed_called());
EXPECT_EQ(RequestCoordinator::BackgroundSavePageResult::REMOVED,
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(
coordinator()->SavePageLater(
kUrl1, kClientId1, kUserRequested,
RequestCoordinator::RequestAvailability::ENABLED_FOR_OFFLINER), 0);
PumpLoop();
// Now whether processing triggered immediately depends on whether test
// is run on svelte device or not.
if (base::SysInfo::IsLowEndDevice()) {
EXPECT_FALSE(is_busy());
} else {
EXPECT_TRUE(is_busy());
}
}
TEST_F(RequestCoordinatorTest,
SavePageStartsProcessingWhenConnectedOnLowEndDeviceIfFlagEnabled) {
// Mark device as low-end device.
SetIsLowEndDeviceForTest(true);
EXPECT_FALSE(offline_pages::IsOfflinePagesSvelteConcurrentLoadingEnabled());
// Make a request.
EXPECT_NE(coordinator()->SavePageLater(
kUrl1, kClientId1, kUserRequested,
RequestCoordinator::RequestAvailability::ENABLED_FOR_OFFLINER),
0);
PumpLoop();
// Verify not immediately busy (since low-end device).
EXPECT_FALSE(is_busy());
// 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.
EXPECT_NE(coordinator()->SavePageLater(
kUrl2, kClientId2, kUserRequested,
RequestCoordinator::RequestAvailability::ENABLED_FOR_OFFLINER),
0);
PumpLoop();
// Verify immediate processing did start this time.
EXPECT_TRUE(is_busy());
}
TEST_F(RequestCoordinatorTest, SavePageDoesntStartProcessingWhenDisconnected) {
SetNetworkConnected(false);
EXPECT_NE(
coordinator()->SavePageLater(
kUrl1, kClientId1, kUserRequested,
RequestCoordinator::RequestAvailability::ENABLED_FOR_OFFLINER), 0);
PumpLoop();
EXPECT_FALSE(is_busy());
}
TEST_F(RequestCoordinatorTest,
SavePageDoesStartProcessingWhenPoorlyConnected) {
// Set specific network type for 2G with poor effective connection.
SetNetworkConditionsForTest(
net::NetworkChangeNotifier::ConnectionType::CONNECTION_2G);
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(
coordinator()->SavePageLater(
kUrl1, kClientId1, kUserRequested,
RequestCoordinator::RequestAvailability::ENABLED_FOR_OFFLINER), 0);
PumpLoop();
EXPECT_TRUE(is_busy());
}
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(is_busy());
// 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(is_busy());
// Pause the request again.
coordinator()->PauseRequests(request_ids);
PumpLoop();
// Now simulate reasonable connection.
SetNetworkConnected(true);
// Resume the request while connected.
coordinator()->ResumeRequests(request_ids);
EXPECT_FALSE(is_busy());
PumpLoop();
// Now whether processing triggered immediately depends on whether test
// is run on svelte device or not.
if (base::SysInfo::IsLowEndDevice()) {
EXPECT_FALSE(is_busy());
} else {
EXPECT_TRUE(is_busy());
}
}
} // namespace offline_pages