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