1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "components/download/internal/background_service/scheduler/scheduler_impl.h"
6 
7 #include <stdint.h>
8 #include <memory>
9 
10 #include "base/strings/string_number_conversions.h"
11 #include "components/download/internal/background_service/config.h"
12 #include "components/download/internal/background_service/entry.h"
13 #include "components/download/internal/background_service/scheduler/device_status.h"
14 #include "components/download/public/task/task_scheduler.h"
15 #include "testing/gmock/include/gmock/gmock.h"
16 #include "testing/gtest/include/gtest/gtest.h"
17 
18 using testing::_;
19 using testing::InSequence;
20 
21 namespace download {
22 namespace {
23 
24 class MockTaskScheduler : public TaskScheduler {
25  public:
26   MockTaskScheduler() = default;
27   ~MockTaskScheduler() override = default;
28 
29   MOCK_METHOD6(ScheduleTask,
30                void(DownloadTaskType, bool, bool, int, int64_t, int64_t));
31   MOCK_METHOD1(CancelTask, void(DownloadTaskType));
32 };
33 
34 class DownloadSchedulerImplTest : public testing::Test {
35  public:
DownloadSchedulerImplTest()36   DownloadSchedulerImplTest() {}
37   ~DownloadSchedulerImplTest() override = default;
38 
TearDown()39   void TearDown() override { DestroyScheduler(); }
40 
BuildScheduler(const std::vector<DownloadClient> clients)41   void BuildScheduler(const std::vector<DownloadClient> clients) {
42     scheduler_ =
43         std::make_unique<SchedulerImpl>(&task_scheduler_, &config_, clients);
44   }
DestroyScheduler()45   void DestroyScheduler() { scheduler_.reset(); }
46 
47   // Helper function to create a list of entries for the scheduler to query the
48   // next entry.
BuildDataEntries(size_t size)49   void BuildDataEntries(size_t size) {
50     entries_ = std::vector<Entry>(size, Entry());
51     for (size_t i = 0; i < size; ++i) {
52       entries_[i].guid = base::NumberToString(i);
53       entries_[i].scheduling_params.battery_requirements =
54           SchedulingParams::BatteryRequirements::BATTERY_SENSITIVE;
55       entries_[i].scheduling_params.network_requirements =
56           SchedulingParams::NetworkRequirements::UNMETERED;
57       entries_[i].state = Entry::State::AVAILABLE;
58     }
59   }
60 
61   // Returns list of entry pointers to feed to the scheduler.
entries()62   Model::EntryList entries() {
63     Model::EntryList entry_list;
64     for (auto& entry : entries_) {
65       entry_list.emplace_back(&entry);
66     }
67     return entry_list;
68   }
69 
70   // Simulates the entry has been processed by the download service and the
71   // state has changed.
MakeEntryActive(Entry * entry)72   void MakeEntryActive(Entry* entry) {
73     if (entry)
74       entry->state = Entry::State::ACTIVE;
75   }
76 
77   // Reverts the states of entry so that the scheduler can poll it again.
MakeEntryAvailable(Entry * entry)78   void MakeEntryAvailable(Entry* entry) {
79     entry->state = Entry::State::AVAILABLE;
80   }
81 
82   // Helper function to build a device status.
BuildDeviceStatus(BatteryStatus battery,NetworkStatus network)83   DeviceStatus BuildDeviceStatus(BatteryStatus battery, NetworkStatus network) {
84     DeviceStatus device_status;
85     device_status.battery_status = battery;
86     device_status.network_status = network;
87     return device_status;
88   }
89 
90  protected:
91   std::unique_ptr<SchedulerImpl> scheduler_;
92   MockTaskScheduler task_scheduler_;
93   Configuration config_;
94 
95   // Entries owned by the test fixture.
96   std::vector<Entry> entries_;
97 
98  private:
99   DISALLOW_COPY_AND_ASSIGN(DownloadSchedulerImplTest);
100 };
101 
102 // Ensures normal polling logic is correct.
TEST_F(DownloadSchedulerImplTest,BasicPolling)103 TEST_F(DownloadSchedulerImplTest, BasicPolling) {
104   BuildScheduler(std::vector<DownloadClient>{DownloadClient::TEST_2,
105                                              DownloadClient::TEST});
106 
107   // Client TEST: entry 0.
108   // Client TEST_2: entry 1.
109   // Poll sequence: 1 -> 0.
110   BuildDataEntries(2);
111   entries_[0].client = DownloadClient::TEST;
112   entries_[1].client = DownloadClient::TEST_2;
113 
114   // First download belongs to first client.
115   Entry* next = scheduler_->Next(
116       entries(),
117       BuildDeviceStatus(BatteryStatus::CHARGING, NetworkStatus::UNMETERED));
118   EXPECT_EQ(next, &entries_[1]);
119   MakeEntryActive(next);
120 
121   // If the first one is processed, the next should be the other entry.
122   next = scheduler_->Next(
123       entries(),
124       BuildDeviceStatus(BatteryStatus::CHARGING, NetworkStatus::UNMETERED));
125   EXPECT_EQ(next, &entries_[0]);
126   MakeEntryActive(next);
127 }
128 
129 // Tests the load balancing and polling downloads based on cancel time.
TEST_F(DownloadSchedulerImplTest,BasicLoadBalancing)130 TEST_F(DownloadSchedulerImplTest, BasicLoadBalancing) {
131   BuildScheduler(std::vector<DownloadClient>{
132       DownloadClient::TEST, DownloadClient::TEST_2, DownloadClient::TEST_3});
133 
134   // Client TEST: entry 0, entry 1 (earlier cancel time).
135   // Client TEST_2: entry 2.
136   // Client TEST_3: No entries.
137   // Poll sequence: 1 -> 2 -> 0.
138   BuildDataEntries(3);
139   entries_[0].client = DownloadClient::TEST;
140   entries_[0].scheduling_params.cancel_time = base::Time::FromInternalValue(20);
141   entries_[1].client = DownloadClient::TEST;
142   entries_[1].scheduling_params.cancel_time = base::Time::FromInternalValue(10);
143   entries_[2].client = DownloadClient::TEST_2;
144   entries_[2].scheduling_params.cancel_time = base::Time::FromInternalValue(30);
145 
146   // There are 2 downloads for client 0, the one with earlier create time will
147   // be the next download.
148   Entry* next = scheduler_->Next(
149       entries(),
150       BuildDeviceStatus(BatteryStatus::CHARGING, NetworkStatus::UNMETERED));
151   EXPECT_EQ(&entries_[1], next);
152   MakeEntryActive(next);
153 
154   // The second download should belongs to client 1, because of the round robin
155   // load balancing.
156   next = scheduler_->Next(
157       entries(),
158       BuildDeviceStatus(BatteryStatus::CHARGING, NetworkStatus::UNMETERED));
159   EXPECT_EQ(&entries_[2], next);
160   MakeEntryActive(next);
161 
162   // Only one entry left, which will be the next.
163   next = scheduler_->Next(
164       entries(),
165       BuildDeviceStatus(BatteryStatus::CHARGING, NetworkStatus::UNMETERED));
166   EXPECT_EQ(&entries_[0], next);
167   MakeEntryActive(next);
168 
169   // Keep polling twice, since no available downloads, both will return nullptr.
170   next = scheduler_->Next(
171       entries(),
172       BuildDeviceStatus(BatteryStatus::CHARGING, NetworkStatus::UNMETERED));
173   EXPECT_EQ(nullptr, next);
174   MakeEntryActive(next);
175 
176   next = scheduler_->Next(
177       entries(),
178       BuildDeviceStatus(BatteryStatus::CHARGING, NetworkStatus::UNMETERED));
179   EXPECT_EQ(nullptr, next);
180   MakeEntryActive(next);
181 }
182 
183 // Ensures downloads are polled based on scheduling parameters and device
184 // status.
TEST_F(DownloadSchedulerImplTest,SchedulingParams)185 TEST_F(DownloadSchedulerImplTest, SchedulingParams) {
186   BuildScheduler(std::vector<DownloadClient>{DownloadClient::TEST});
187   BuildDataEntries(1);
188   entries_[0].client = DownloadClient::TEST;
189   entries_[0].scheduling_params.battery_requirements =
190       SchedulingParams::BatteryRequirements::BATTERY_SENSITIVE;
191   entries_[0].scheduling_params.network_requirements =
192       SchedulingParams::NetworkRequirements::UNMETERED;
193 
194   Entry* next = nullptr;
195 
196   // Tests network scheduling parameter.
197   // No downloads can be polled when network disconnected.
198   next = scheduler_->Next(
199       entries(),
200       BuildDeviceStatus(BatteryStatus::CHARGING, NetworkStatus::DISCONNECTED));
201   EXPECT_EQ(nullptr, next);
202 
203   // If the network is metered, and scheduling parameter requires unmetered
204   // network, the download should not be polled.
205   next = scheduler_->Next(entries(), BuildDeviceStatus(BatteryStatus::CHARGING,
206                                                        NetworkStatus::METERED));
207   EXPECT_EQ(nullptr, next);
208 
209   // If the network requirement is none, the download can happen under metered
210   // network. However, download won't happen when network is disconnected.
211   entries_[0].scheduling_params.network_requirements =
212       SchedulingParams::NetworkRequirements::NONE;
213   next = scheduler_->Next(entries(), BuildDeviceStatus(BatteryStatus::CHARGING,
214                                                        NetworkStatus::METERED));
215   EXPECT_EQ(&entries_[0], next);
216   next = scheduler_->Next(
217       entries(),
218       BuildDeviceStatus(BatteryStatus::CHARGING, NetworkStatus::DISCONNECTED));
219   EXPECT_EQ(nullptr, next);
220   MakeEntryActive(next);
221 
222   // Tests battery sensitive scheduling parameter.
223   entries_[0].scheduling_params.battery_requirements =
224       SchedulingParams::BatteryRequirements::BATTERY_SENSITIVE;
225 
226   next = scheduler_->Next(
227       entries(),
228       BuildDeviceStatus(BatteryStatus::CHARGING, NetworkStatus::UNMETERED));
229   EXPECT_EQ(&entries_[0], next);
230 
231   // Battery sensitive with low battery level.
232   DeviceStatus status =
233       BuildDeviceStatus(BatteryStatus::NOT_CHARGING, NetworkStatus::UNMETERED);
234   DCHECK_EQ(status.battery_percentage, 0);
235   next = scheduler_->Next(entries(), status);
236   EXPECT_EQ(nullptr, next);
237 
238   status.battery_percentage = config_.download_battery_percentage - 1;
239   next = scheduler_->Next(entries(), status);
240   EXPECT_EQ(nullptr, next);
241 
242   // Battery sensitive with high battery level.
243   status.battery_percentage = config_.download_battery_percentage;
244   next = scheduler_->Next(entries(), status);
245   EXPECT_EQ(&entries_[0], next);
246 
247   // Tests battery insensitive scheduling parameter.
248   entries_[0].scheduling_params.battery_requirements =
249       SchedulingParams::BatteryRequirements::BATTERY_INSENSITIVE;
250   next = scheduler_->Next(
251       entries(),
252       BuildDeviceStatus(BatteryStatus::NOT_CHARGING, NetworkStatus::UNMETERED));
253   EXPECT_EQ(&entries_[0], next);
254   next = scheduler_->Next(
255       entries(),
256       BuildDeviceStatus(BatteryStatus::CHARGING, NetworkStatus::UNMETERED));
257   EXPECT_EQ(&entries_[0], next);
258   MakeEntryActive(next);
259 }
260 
261 // Ensures higher priority will be scheduled first.
TEST_F(DownloadSchedulerImplTest,Priority)262 TEST_F(DownloadSchedulerImplTest, Priority) {
263   BuildScheduler(std::vector<DownloadClient>{DownloadClient::TEST});
264 
265   // The second entry has higher priority but is created later than the first
266   // entry. This ensures priority is checked before the create time.
267   BuildDataEntries(2);
268   entries_[0].client = DownloadClient::TEST;
269   entries_[0].scheduling_params.priority = SchedulingParams::Priority::LOW;
270   entries_[0].scheduling_params.cancel_time = base::Time::FromInternalValue(20);
271   entries_[1].client = DownloadClient::TEST;
272   entries_[1].scheduling_params.priority = SchedulingParams::Priority::HIGH;
273   entries_[1].scheduling_params.cancel_time = base::Time::FromInternalValue(40);
274 
275   // Download with higher priority should be polled first, even if there is
276   // another download created earlier.
277   Entry* next = scheduler_->Next(
278       entries(),
279       BuildDeviceStatus(BatteryStatus::CHARGING, NetworkStatus::UNMETERED));
280   EXPECT_EQ(&entries_[1], next);
281 
282   // Download with non UI priority should be subject to network and battery
283   // scheduling parameters. The higher priority one will be ignored because of
284   // mismatching battery condition.
285   entries_[1].scheduling_params.battery_requirements =
286       SchedulingParams::BatteryRequirements::BATTERY_SENSITIVE;
287   entries_[0].scheduling_params.battery_requirements =
288       SchedulingParams::BatteryRequirements::BATTERY_INSENSITIVE;
289 
290   next = scheduler_->Next(
291       entries(),
292       BuildDeviceStatus(BatteryStatus::NOT_CHARGING, NetworkStatus::UNMETERED));
293   EXPECT_EQ(&entries_[0], next);
294   MakeEntryActive(next);
295 }
296 
297 // Ensures UI priority entries are subject to device status check.
TEST_F(DownloadSchedulerImplTest,UIPrioritySubjectToDeviceStatus)298 TEST_F(DownloadSchedulerImplTest, UIPrioritySubjectToDeviceStatus) {
299   BuildScheduler(std::vector<DownloadClient>{DownloadClient::TEST,
300                                              DownloadClient::TEST_2});
301 
302   // Client TEST: entry 0.
303   // Client TEST_2: entry 1 (UI priority, cancel later).
304   BuildDataEntries(2);
305   entries_[0].client = DownloadClient::TEST;
306   entries_[0].scheduling_params.priority = SchedulingParams::Priority::LOW;
307   entries_[1].client = DownloadClient::TEST_2;
308   entries_[1].scheduling_params.priority = SchedulingParams::Priority::UI;
309 
310   // UI priority is also subject to device status validation.
311   Entry* next = scheduler_->Next(
312       entries(),
313       BuildDeviceStatus(BatteryStatus::NOT_CHARGING, NetworkStatus::METERED));
314   EXPECT_EQ(nullptr, next);
315   MakeEntryActive(next);
316 }
317 
318 // UI priority entries will be processed first even if they doesn't belong to
319 // the current client in load balancing.
TEST_F(DownloadSchedulerImplTest,UIPriorityLoadBalancing)320 TEST_F(DownloadSchedulerImplTest, UIPriorityLoadBalancing) {
321   BuildScheduler(std::vector<DownloadClient>{DownloadClient::TEST,
322                                              DownloadClient::TEST_2});
323 
324   // Client TEST: entry 0(Low priority).
325   // Client TEST_2: entry 1(UI priority).
326   BuildDataEntries(2);
327   entries_[0].client = DownloadClient::TEST;
328   entries_[0].scheduling_params.priority = SchedulingParams::Priority::LOW;
329   entries_[1].client = DownloadClient::TEST_2;
330   entries_[1].scheduling_params.priority = SchedulingParams::Priority::UI;
331 
332   Entry* next = scheduler_->Next(
333       entries(),
334       BuildDeviceStatus(BatteryStatus::CHARGING, NetworkStatus::UNMETERED));
335   EXPECT_EQ(&entries_[1], next);
336   MakeEntryActive(next);
337 }
338 
TEST_F(DownloadSchedulerImplTest,PickOlderDownloadIfSameParameters)339 TEST_F(DownloadSchedulerImplTest, PickOlderDownloadIfSameParameters) {
340   BuildScheduler(std::vector<DownloadClient>{DownloadClient::TEST,
341                                              DownloadClient::TEST_2});
342 
343   // Client TEST: entry 0(Low priority, No Cancel Time, Newer).
344   // Client TEST: entry 1(Low priority, No Cancel Time, Older).
345   // Client TEST: entry 2(Low priority, No Cancel Time, Newer).
346   BuildDataEntries(3);
347   entries_[0].client = DownloadClient::TEST;
348   entries_[0].scheduling_params.priority = SchedulingParams::Priority::LOW;
349   entries_[0].create_time = base::Time::Now();
350   entries_[1].client = DownloadClient::TEST;
351   entries_[1].scheduling_params.priority = SchedulingParams::Priority::LOW;
352   entries_[1].create_time = base::Time::Now() - base::TimeDelta::FromDays(1);
353   entries_[2].client = DownloadClient::TEST;
354   entries_[2].scheduling_params.priority = SchedulingParams::Priority::LOW;
355   entries_[2].create_time = base::Time::Now();
356 
357   Entry* next = scheduler_->Next(
358       entries(),
359       BuildDeviceStatus(BatteryStatus::CHARGING, NetworkStatus::UNMETERED));
360   EXPECT_EQ(&entries_[1], next);
361   MakeEntryActive(next);
362 }
363 
364 // When multiple UI priority entries exist, the next entry is selected based on
365 // cancel time and load balancing.
TEST_F(DownloadSchedulerImplTest,MultipleUIPriorityEntries)366 TEST_F(DownloadSchedulerImplTest, MultipleUIPriorityEntries) {
367   BuildScheduler(std::vector<DownloadClient>{DownloadClient::TEST,
368                                              DownloadClient::TEST_2});
369   BuildDataEntries(4);
370 
371   // Client TEST: entry 0(UI priority), entry 1(UI priority, early cancel time).
372   // Client TEST_2: entry 2(UI priority), entry 3(high priority, early cancel
373   // time). Poll sequence: 1 -> 2 -> 0 -> 3.
374   for (auto& entry : entries_) {
375     entry.scheduling_params.priority = SchedulingParams::Priority::UI;
376   }
377   entries_[0].client = DownloadClient::TEST;
378   entries_[0].scheduling_params.cancel_time = base::Time::FromInternalValue(40);
379   entries_[1].client = DownloadClient::TEST;
380   entries_[1].scheduling_params.cancel_time = base::Time::FromInternalValue(20);
381   entries_[2].client = DownloadClient::TEST_2;
382   entries_[2].scheduling_params.cancel_time = base::Time::FromInternalValue(50);
383   entries_[3].client = DownloadClient::TEST_2;
384   entries_[3].scheduling_params.cancel_time = base::Time::FromInternalValue(20);
385   entries_[3].scheduling_params.priority = SchedulingParams::Priority::HIGH;
386 
387   // When device conditions are meet, UI priority entry with the earliest cancel
388   // time will be processed first.
389   Entry* next = scheduler_->Next(
390       entries(),
391       BuildDeviceStatus(BatteryStatus::CHARGING, NetworkStatus::UNMETERED));
392   EXPECT_EQ(&entries_[1], next);
393   MakeEntryActive(next);
394 
395   // Next entry will be UI priority entry from another client.
396   next = scheduler_->Next(
397       entries(),
398       BuildDeviceStatus(BatteryStatus::CHARGING, NetworkStatus::UNMETERED));
399   EXPECT_EQ(&entries_[2], next);
400   MakeEntryActive(next);
401 
402   next = scheduler_->Next(
403       entries(),
404       BuildDeviceStatus(BatteryStatus::CHARGING, NetworkStatus::UNMETERED));
405   EXPECT_EQ(&entries_[0], next);
406   MakeEntryActive(next);
407 
408   next = scheduler_->Next(
409       entries(),
410       BuildDeviceStatus(BatteryStatus::CHARGING, NetworkStatus::UNMETERED));
411   EXPECT_EQ(&entries_[3], next);
412   MakeEntryActive(next);
413 }
414 
415 // Ensures the reschedule logic works correctly, and we can pass the correct
416 // criteria to platform task scheduler.
TEST_F(DownloadSchedulerImplTest,Reschedule)417 TEST_F(DownloadSchedulerImplTest, Reschedule) {
418   InSequence s;
419 
420   BuildScheduler(std::vector<DownloadClient>{DownloadClient::TEST});
421   BuildDataEntries(2);
422   entries_[0].client = DownloadClient::TEST;
423   entries_[1].client = DownloadClient::TEST;
424 
425   entries_[0].scheduling_params.battery_requirements =
426       SchedulingParams::BatteryRequirements::BATTERY_CHARGING;
427   entries_[0].scheduling_params.network_requirements =
428       SchedulingParams::NetworkRequirements::UNMETERED;
429   entries_[1].scheduling_params.battery_requirements =
430       SchedulingParams::BatteryRequirements::BATTERY_CHARGING;
431   entries_[1].scheduling_params.network_requirements =
432       SchedulingParams::NetworkRequirements::UNMETERED;
433 
434   Criteria criteria(config_.download_battery_percentage);
435   EXPECT_CALL(task_scheduler_, CancelTask(DownloadTaskType::DOWNLOAD_TASK))
436       .Times(0);
437   EXPECT_CALL(task_scheduler_,
438               ScheduleTask(DownloadTaskType::DOWNLOAD_TASK,
439                            criteria.requires_unmetered_network,
440                            criteria.requires_battery_charging, _, _, _))
441       .RetiresOnSaturation();
442   scheduler_->Reschedule(entries());
443 
444   entries_[0].scheduling_params.battery_requirements =
445       SchedulingParams::BatteryRequirements::BATTERY_INSENSITIVE;
446   criteria.requires_battery_charging = false;
447   EXPECT_CALL(task_scheduler_, CancelTask(DownloadTaskType::DOWNLOAD_TASK))
448       .Times(0);
449   EXPECT_CALL(task_scheduler_,
450               ScheduleTask(DownloadTaskType::DOWNLOAD_TASK,
451                            criteria.requires_unmetered_network,
452                            criteria.requires_battery_charging, _, _, _))
453       .RetiresOnSaturation();
454   scheduler_->Reschedule(entries());
455 
456   entries_[0].scheduling_params.network_requirements =
457       SchedulingParams::NetworkRequirements::NONE;
458   criteria.requires_unmetered_network = false;
459   EXPECT_CALL(task_scheduler_, CancelTask(DownloadTaskType::DOWNLOAD_TASK))
460       .Times(0);
461   EXPECT_CALL(task_scheduler_,
462               ScheduleTask(DownloadTaskType::DOWNLOAD_TASK,
463                            criteria.requires_unmetered_network,
464                            criteria.requires_battery_charging, _, _, _))
465       .RetiresOnSaturation();
466   scheduler_->Reschedule(entries());
467 
468   EXPECT_CALL(task_scheduler_, CancelTask(DownloadTaskType::DOWNLOAD_TASK))
469       .RetiresOnSaturation();
470   scheduler_->Reschedule(Model::EntryList());
471 }
472 
473 }  // namespace
474 }  // namespace download
475