1// Copyright (c) 2012 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 "base/memory/scoped_vector.h"
6#include "base/memory/weak_ptr.h"
7#include "base/message_loop/message_loop.h"
8#include "base/stl_util.h"
9#include "chrome/browser/download/download_status_updater.h"
10#include "content/public/test/mock_download_item.h"
11#include "content/public/test/mock_download_manager.h"
12#include "content/public/test/test_browser_thread.h"
13#include "testing/gmock/include/gmock/gmock.h"
14#include "testing/gtest/include/gtest/gtest.h"
15
16using testing::AtLeast;
17using testing::Invoke;
18using testing::Mock;
19using testing::Return;
20using testing::SetArgPointee;
21using testing::StrictMock;
22using testing::WithArg;
23using testing::_;
24
25class TestDownloadStatusUpdater : public DownloadStatusUpdater {
26 public:
27  TestDownloadStatusUpdater() : notification_count_(0),
28                                acceptable_notification_item_(NULL) {
29  }
30  void SetAcceptableNotificationItem(content::DownloadItem* item) {
31    acceptable_notification_item_ = item;
32  }
33  size_t NotificationCount() {
34    return notification_count_;
35  }
36 protected:
37  virtual void UpdateAppIconDownloadProgress(
38      content::DownloadItem* download) OVERRIDE {
39    ++notification_count_;
40    if (acceptable_notification_item_)
41      EXPECT_EQ(acceptable_notification_item_, download);
42  }
43 private:
44  size_t notification_count_;
45  content::DownloadItem* acceptable_notification_item_;
46};
47
48class DownloadStatusUpdaterTest : public testing::Test {
49 public:
50  DownloadStatusUpdaterTest()
51      : updater_(new TestDownloadStatusUpdater()),
52        ui_thread_(content::BrowserThread::UI, &loop_) {}
53
54  virtual ~DownloadStatusUpdaterTest() {
55    for (size_t mgr_idx = 0; mgr_idx < managers_.size(); ++mgr_idx) {
56      EXPECT_CALL(*Manager(mgr_idx), RemoveObserver(_));
57    }
58
59    delete updater_;
60    updater_ = NULL;
61    VerifyAndClearExpectations();
62
63    managers_.clear();
64    for (std::vector<Items>::iterator it = manager_items_.begin();
65         it != manager_items_.end(); ++it)
66      STLDeleteContainerPointers(it->begin(), it->end());
67
68    loop_.RunUntilIdle();  // Allow DownloadManager destruction.
69  }
70
71 protected:
72  // Attach some number of DownloadManagers to the updater.
73  void SetupManagers(int manager_count) {
74    DCHECK_EQ(0U, managers_.size());
75    for (int i = 0; i < manager_count; ++i) {
76      content::MockDownloadManager* mgr =
77          new StrictMock<content::MockDownloadManager>;
78      managers_.push_back(mgr);
79    }
80  }
81
82  void SetObserver(content::DownloadManager::Observer* observer) {
83    manager_observers_[manager_observer_index_] = observer;
84  }
85
86  // Hook the specified manager into the updater.
87  void LinkManager(int i) {
88    content::MockDownloadManager* mgr = managers_[i];
89    manager_observer_index_ = i;
90    while (manager_observers_.size() <= static_cast<size_t>(i)) {
91      manager_observers_.push_back(NULL);
92    }
93    EXPECT_CALL(*mgr, AddObserver(_))
94        .WillOnce(WithArg<0>(Invoke(
95            this, &DownloadStatusUpdaterTest::SetObserver)));
96    updater_->AddManager(mgr);
97  }
98
99  // Add some number of Download items to a particular manager.
100  void AddItems(int manager_index, int item_count, int in_progress_count) {
101    DCHECK_GT(managers_.size(), static_cast<size_t>(manager_index));
102    content::MockDownloadManager* manager = managers_[manager_index];
103
104    if (manager_items_.size() <= static_cast<size_t>(manager_index))
105      manager_items_.resize(manager_index+1);
106
107    std::vector<content::DownloadItem*> item_list;
108    for (int i = 0; i < item_count; ++i) {
109      content::MockDownloadItem* item =
110          new StrictMock<content::MockDownloadItem>;
111      content::DownloadItem::DownloadState state =
112          i < in_progress_count ? content::DownloadItem::IN_PROGRESS
113              : content::DownloadItem::CANCELLED;
114      EXPECT_CALL(*item, GetState()).WillRepeatedly(Return(state));
115      manager_items_[manager_index].push_back(item);
116    }
117    EXPECT_CALL(*manager, GetAllDownloads(_))
118        .WillRepeatedly(SetArgPointee<0>(manager_items_[manager_index]));
119  }
120
121  // Return the specified manager.
122  content::MockDownloadManager* Manager(int manager_index) {
123    DCHECK_GT(managers_.size(), static_cast<size_t>(manager_index));
124    return managers_[manager_index];
125  }
126
127  // Return the specified item.
128  content::MockDownloadItem* Item(int manager_index, int item_index) {
129    DCHECK_GT(manager_items_.size(), static_cast<size_t>(manager_index));
130    DCHECK_GT(manager_items_[manager_index].size(),
131              static_cast<size_t>(item_index));
132    // All DownloadItems in manager_items_ are MockDownloadItems.
133    return static_cast<content::MockDownloadItem*>(
134        manager_items_[manager_index][item_index]);
135  }
136
137  // Set return values relevant to |DownloadStatusUpdater::GetProgress()|
138  // for the specified item.
139  void SetItemValues(int manager_index, int item_index,
140                     int received_bytes, int total_bytes, bool notify) {
141    content::MockDownloadItem* item(Item(manager_index, item_index));
142    EXPECT_CALL(*item, GetReceivedBytes())
143        .WillRepeatedly(Return(received_bytes));
144    EXPECT_CALL(*item, GetTotalBytes())
145        .WillRepeatedly(Return(total_bytes));
146    if (notify)
147      updater_->OnDownloadUpdated(managers_[manager_index], item);
148  }
149
150  // Transition specified item to completed.
151  void CompleteItem(int manager_index, int item_index) {
152    content::MockDownloadItem* item(Item(manager_index, item_index));
153    EXPECT_CALL(*item, GetState())
154        .WillRepeatedly(Return(content::DownloadItem::COMPLETE));
155    updater_->OnDownloadUpdated(managers_[manager_index], item);
156  }
157
158  // Verify and clear all mocks expectations.
159  void VerifyAndClearExpectations() {
160    for (ScopedVector<content::MockDownloadManager>::iterator it
161             = managers_.begin(); it != managers_.end(); ++it)
162      Mock::VerifyAndClearExpectations(*it);
163    for (std::vector<Items>::iterator it = manager_items_.begin();
164         it != manager_items_.end(); ++it)
165      for (Items::iterator sit = it->begin(); sit != it->end(); ++sit)
166        Mock::VerifyAndClearExpectations(*sit);
167  }
168
169  ScopedVector<content::MockDownloadManager> managers_;
170  // DownloadItem so that it can be assigned to the result of SearchDownloads.
171  typedef std::vector<content::DownloadItem*> Items;
172  std::vector<Items> manager_items_;
173  int manager_observer_index_;
174
175  std::vector<content::DownloadManager::Observer*> manager_observers_;
176
177  // Pointer so we can verify that destruction triggers appropriate
178  // changes.
179  TestDownloadStatusUpdater *updater_;
180
181  // Thread so that the DownloadManager (which is a DeleteOnUIThread
182  // object) can be deleted.
183  // TODO(rdsmith): This can be removed when the DownloadManager
184  // is no longer required to be deleted on the UI thread.
185  base::MessageLoop loop_;
186  content::TestBrowserThread ui_thread_;
187};
188
189// Test null updater.
190TEST_F(DownloadStatusUpdaterTest, Basic) {
191  float progress = -1;
192  int download_count = -1;
193  EXPECT_TRUE(updater_->GetProgress(&progress, &download_count));
194  EXPECT_FLOAT_EQ(0.0f, progress);
195  EXPECT_EQ(0, download_count);
196}
197
198// Test updater with null manager.
199TEST_F(DownloadStatusUpdaterTest, OneManagerNoItems) {
200  SetupManagers(1);
201  AddItems(0, 0, 0);
202  LinkManager(0);
203  VerifyAndClearExpectations();
204
205  float progress = -1;
206  int download_count = -1;
207  EXPECT_CALL(*managers_[0], GetAllDownloads(_))
208      .WillRepeatedly(SetArgPointee<0>(manager_items_[0]));
209  EXPECT_TRUE(updater_->GetProgress(&progress, &download_count));
210  EXPECT_FLOAT_EQ(0.0f, progress);
211  EXPECT_EQ(0, download_count);
212}
213
214// Test updater with non-null manager, including transition an item to
215// |content::DownloadItem::COMPLETE| and adding a new item.
216TEST_F(DownloadStatusUpdaterTest, OneManagerManyItems) {
217  SetupManagers(1);
218  AddItems(0, 3, 2);
219  LinkManager(0);
220
221  // Prime items
222  SetItemValues(0, 0, 10, 20, false);
223  SetItemValues(0, 1, 50, 60, false);
224  SetItemValues(0, 2, 90, 90, false);
225
226  float progress = -1;
227  int download_count = -1;
228  EXPECT_TRUE(updater_->GetProgress(&progress, &download_count));
229  EXPECT_FLOAT_EQ((10+50)/(20.0f+60), progress);
230  EXPECT_EQ(2, download_count);
231
232  // Transition one item to completed and confirm progress is updated
233  // properly.
234  CompleteItem(0, 0);
235  EXPECT_TRUE(updater_->GetProgress(&progress, &download_count));
236  EXPECT_FLOAT_EQ(50/60.0f, progress);
237  EXPECT_EQ(1, download_count);
238
239  // Add a new item to manager and confirm progress is updated properly.
240  AddItems(0, 1, 1);
241  SetItemValues(0, 3, 150, 200, false);
242  manager_observers_[0]->OnDownloadCreated(
243      managers_[0], manager_items_[0][manager_items_[0].size()-1]);
244
245  EXPECT_TRUE(updater_->GetProgress(&progress, &download_count));
246  EXPECT_FLOAT_EQ((50+150)/(60+200.0f), progress);
247  EXPECT_EQ(2, download_count);
248}
249
250// Test to ensure that the download progress notification is called correctly.
251TEST_F(DownloadStatusUpdaterTest, ProgressNotification) {
252  size_t expected_notifications = updater_->NotificationCount();
253  SetupManagers(1);
254  AddItems(0, 2, 2);
255  LinkManager(0);
256
257  // Expect two notifications, one for each item; which item will come first
258  // isn't defined so it cannot be tested.
259  expected_notifications += 2;
260  ASSERT_EQ(expected_notifications, updater_->NotificationCount());
261
262  // Make progress on the first item.
263  updater_->SetAcceptableNotificationItem(Item(0, 0));
264  SetItemValues(0, 0, 10, 20, true);
265  ++expected_notifications;
266  ASSERT_EQ(expected_notifications, updater_->NotificationCount());
267
268  // Second item completes!
269  updater_->SetAcceptableNotificationItem(Item(0, 1));
270  CompleteItem(0, 1);
271  ++expected_notifications;
272  ASSERT_EQ(expected_notifications, updater_->NotificationCount());
273
274  // First item completes.
275  updater_->SetAcceptableNotificationItem(Item(0, 0));
276  CompleteItem(0, 0);
277  ++expected_notifications;
278  ASSERT_EQ(expected_notifications, updater_->NotificationCount());
279
280  updater_->SetAcceptableNotificationItem(NULL);
281}
282
283// Confirm we recognize the situation where we have an unknown size.
284TEST_F(DownloadStatusUpdaterTest, UnknownSize) {
285  SetupManagers(1);
286  AddItems(0, 2, 2);
287  LinkManager(0);
288
289  // Prime items
290  SetItemValues(0, 0, 10, 20, false);
291  SetItemValues(0, 1, 50, -1, false);
292
293  float progress = -1;
294  int download_count = -1;
295  EXPECT_FALSE(updater_->GetProgress(&progress, &download_count));
296}
297
298// Test many null managers.
299TEST_F(DownloadStatusUpdaterTest, ManyManagersNoItems) {
300  SetupManagers(1);
301  AddItems(0, 0, 0);
302  LinkManager(0);
303
304  float progress = -1;
305  int download_count = -1;
306  EXPECT_TRUE(updater_->GetProgress(&progress, &download_count));
307  EXPECT_FLOAT_EQ(0.0f, progress);
308  EXPECT_EQ(0, download_count);
309}
310
311// Test many managers with all items complete.
312TEST_F(DownloadStatusUpdaterTest, ManyManagersEmptyItems) {
313  SetupManagers(2);
314  AddItems(0, 3, 0);
315  LinkManager(0);
316  AddItems(1, 3, 0);
317  LinkManager(1);
318
319  float progress = -1;
320  int download_count = -1;
321  EXPECT_TRUE(updater_->GetProgress(&progress, &download_count));
322  EXPECT_FLOAT_EQ(0.0f, progress);
323  EXPECT_EQ(0, download_count);
324}
325
326// Test many managers with some non-complete items.
327TEST_F(DownloadStatusUpdaterTest, ManyManagersMixedItems) {
328  SetupManagers(2);
329  AddItems(0, 3, 2);
330  LinkManager(0);
331  AddItems(1, 3, 1);
332  LinkManager(1);
333
334  SetItemValues(0, 0, 10, 20, false);
335  SetItemValues(0, 1, 50, 60, false);
336  SetItemValues(1, 0, 80, 90, false);
337
338  float progress = -1;
339  int download_count = -1;
340  EXPECT_TRUE(updater_->GetProgress(&progress, &download_count));
341  EXPECT_FLOAT_EQ((10+50+80)/(20.0f+60+90), progress);
342  EXPECT_EQ(3, download_count);
343}
344