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 "content/public/test/download_test_observer.h"
6
7#include <vector>
8
9#include "base/bind.h"
10#include "base/logging.h"
11#include "base/message_loop/message_loop.h"
12#include "base/stl_util.h"
13#include "base/threading/sequenced_worker_pool.h"
14#include "content/public/browser/browser_thread.h"
15#include "content/public/browser/download_url_parameters.h"
16#include "content/public/test/test_utils.h"
17#include "testing/gtest/include/gtest/gtest.h"
18
19namespace content {
20
21DownloadUpdatedObserver::DownloadUpdatedObserver(
22    DownloadItem* item, DownloadUpdatedObserver::EventFilter filter)
23    : item_(item),
24      filter_(filter),
25      waiting_(false),
26      event_seen_(false) {
27  item->AddObserver(this);
28}
29
30DownloadUpdatedObserver::~DownloadUpdatedObserver() {
31  if (item_)
32    item_->RemoveObserver(this);
33}
34
35bool DownloadUpdatedObserver::WaitForEvent() {
36  if (item_ && filter_.Run(item_))
37    event_seen_ = true;
38  if (event_seen_)
39    return true;
40
41  waiting_ = true;
42  RunMessageLoop();
43  waiting_ = false;
44  return event_seen_;
45}
46
47void DownloadUpdatedObserver::OnDownloadUpdated(DownloadItem* item) {
48  DCHECK_EQ(item_, item);
49  if (filter_.Run(item_))
50    event_seen_ = true;
51  if (waiting_ && event_seen_)
52    base::MessageLoopForUI::current()->Quit();
53}
54
55void DownloadUpdatedObserver::OnDownloadDestroyed(DownloadItem* item) {
56  DCHECK_EQ(item_, item);
57  item_->RemoveObserver(this);
58  item_ = NULL;
59  if (waiting_)
60    base::MessageLoopForUI::current()->Quit();
61}
62
63DownloadTestObserver::DownloadTestObserver(
64    DownloadManager* download_manager,
65    size_t wait_count,
66    DangerousDownloadAction dangerous_download_action)
67    : download_manager_(download_manager),
68      wait_count_(wait_count),
69      finished_downloads_at_construction_(0),
70      waiting_(false),
71      dangerous_download_action_(dangerous_download_action),
72      weak_factory_(this) {
73}
74
75DownloadTestObserver::~DownloadTestObserver() {
76  for (DownloadSet::iterator it = downloads_observed_.begin();
77       it != downloads_observed_.end(); ++it)
78    (*it)->RemoveObserver(this);
79
80  if (download_manager_)
81    download_manager_->RemoveObserver(this);
82}
83
84void DownloadTestObserver::Init() {
85  download_manager_->AddObserver(this);
86  std::vector<DownloadItem*> downloads;
87  download_manager_->GetAllDownloads(&downloads);
88  for (std::vector<DownloadItem*>::iterator it = downloads.begin();
89       it != downloads.end(); ++it) {
90    OnDownloadCreated(download_manager_, *it);
91  }
92  finished_downloads_at_construction_ = finished_downloads_.size();
93  states_observed_.clear();
94}
95
96void DownloadTestObserver::ManagerGoingDown(DownloadManager* manager) {
97  CHECK_EQ(manager, download_manager_);
98  download_manager_ = NULL;
99  SignalIfFinished();
100}
101
102void DownloadTestObserver::WaitForFinished() {
103  if (!IsFinished()) {
104    waiting_ = true;
105    RunMessageLoop();
106    waiting_ = false;
107  }
108}
109
110bool DownloadTestObserver::IsFinished() const {
111  return (finished_downloads_.size() - finished_downloads_at_construction_ >=
112          wait_count_) || (download_manager_ == NULL);
113}
114
115void DownloadTestObserver::OnDownloadCreated(
116    DownloadManager* manager,
117    DownloadItem* item) {
118  // NOTE: This method is called both by DownloadManager when a download is
119  // created as well as in DownloadTestObserver::Init() for downloads that
120  // existed before |this| was created.
121  OnDownloadUpdated(item);
122  DownloadSet::const_iterator finished_it(finished_downloads_.find(item));
123  // If it isn't finished, start observing it.
124  if (finished_it == finished_downloads_.end()) {
125    item->AddObserver(this);
126    downloads_observed_.insert(item);
127  }
128}
129
130void DownloadTestObserver::OnDownloadDestroyed(DownloadItem* download) {
131  // Stop observing.  Do not do anything with it, as it is about to be gone.
132  DownloadSet::iterator it = downloads_observed_.find(download);
133  ASSERT_TRUE(it != downloads_observed_.end());
134  downloads_observed_.erase(it);
135  download->RemoveObserver(this);
136}
137
138void DownloadTestObserver::OnDownloadUpdated(DownloadItem* download) {
139  // Real UI code gets the user's response after returning from the observer.
140  if (download->IsDangerous() &&
141      !ContainsKey(dangerous_downloads_seen_, download->GetId())) {
142    dangerous_downloads_seen_.insert(download->GetId());
143
144    // Calling ValidateDangerousDownload() at this point will
145    // cause the download to be completed twice.  Do what the real UI
146    // code does: make the call as a delayed task.
147    switch (dangerous_download_action_) {
148      case ON_DANGEROUS_DOWNLOAD_ACCEPT:
149        // Fake user click on "Accept".  Delay the actual click, as the
150        // real UI would.
151        BrowserThread::PostTask(
152            BrowserThread::UI, FROM_HERE,
153            base::Bind(&DownloadTestObserver::AcceptDangerousDownload,
154                       weak_factory_.GetWeakPtr(),
155                       download->GetId()));
156        break;
157
158      case ON_DANGEROUS_DOWNLOAD_DENY:
159        // Fake a user click on "Deny".  Delay the actual click, as the
160        // real UI would.
161        BrowserThread::PostTask(
162            BrowserThread::UI, FROM_HERE,
163            base::Bind(&DownloadTestObserver::DenyDangerousDownload,
164                       weak_factory_.GetWeakPtr(),
165                       download->GetId()));
166        break;
167
168      case ON_DANGEROUS_DOWNLOAD_FAIL:
169        ADD_FAILURE() << "Unexpected dangerous download item.";
170        break;
171
172      case ON_DANGEROUS_DOWNLOAD_IGNORE:
173        break;
174
175      default:
176        NOTREACHED();
177    }
178  }
179
180  if (IsDownloadInFinalState(download))
181    DownloadInFinalState(download);
182}
183
184size_t DownloadTestObserver::NumDangerousDownloadsSeen() const {
185  return dangerous_downloads_seen_.size();
186}
187
188size_t DownloadTestObserver::NumDownloadsSeenInState(
189    DownloadItem::DownloadState state) const {
190  StateMap::const_iterator it = states_observed_.find(state);
191
192  if (it == states_observed_.end())
193    return 0;
194
195  return it->second;
196}
197
198void DownloadTestObserver::DownloadInFinalState(DownloadItem* download) {
199  if (finished_downloads_.find(download) != finished_downloads_.end()) {
200    // We've already seen the final state on this download.
201    return;
202  }
203
204  // Record the transition.
205  finished_downloads_.insert(download);
206
207  // Record the state.
208  states_observed_[download->GetState()]++;  // Initializes to 0 the first time.
209
210  SignalIfFinished();
211}
212
213void DownloadTestObserver::SignalIfFinished() {
214  if (waiting_ && IsFinished())
215    base::MessageLoopForUI::current()->Quit();
216}
217
218void DownloadTestObserver::AcceptDangerousDownload(uint32 download_id) {
219  // Download manager was shutdown before the UI thread could accept the
220  // download.
221  if (!download_manager_)
222    return;
223  DownloadItem* download = download_manager_->GetDownload(download_id);
224  if (download && !download->IsDone())
225    download->ValidateDangerousDownload();
226}
227
228void DownloadTestObserver::DenyDangerousDownload(uint32 download_id) {
229  // Download manager was shutdown before the UI thread could deny the
230  // download.
231  if (!download_manager_)
232    return;
233  DownloadItem* download = download_manager_->GetDownload(download_id);
234  if (download && !download->IsDone())
235    download->Remove();
236}
237
238DownloadTestObserverTerminal::DownloadTestObserverTerminal(
239    DownloadManager* download_manager,
240    size_t wait_count,
241    DangerousDownloadAction dangerous_download_action)
242        : DownloadTestObserver(download_manager,
243                               wait_count,
244                               dangerous_download_action) {
245  // You can't rely on overriden virtual functions in a base class constructor;
246  // the virtual function table hasn't been set up yet.  So, we have to do any
247  // work that depends on those functions in the derived class constructor
248  // instead.  In this case, it's because of |IsDownloadInFinalState()|.
249  Init();
250}
251
252DownloadTestObserverTerminal::~DownloadTestObserverTerminal() {
253}
254
255
256bool DownloadTestObserverTerminal::IsDownloadInFinalState(
257    DownloadItem* download) {
258  return download->IsDone();
259}
260
261DownloadTestObserverInProgress::DownloadTestObserverInProgress(
262    DownloadManager* download_manager,
263    size_t wait_count)
264        : DownloadTestObserver(download_manager,
265                               wait_count,
266                               ON_DANGEROUS_DOWNLOAD_ACCEPT) {
267  // You can't override virtual functions in a base class constructor; the
268  // virtual function table hasn't been set up yet.  So, we have to do any
269  // work that depends on those functions in the derived class constructor
270  // instead.  In this case, it's because of |IsDownloadInFinalState()|.
271  Init();
272}
273
274DownloadTestObserverInProgress::~DownloadTestObserverInProgress() {
275}
276
277
278bool DownloadTestObserverInProgress::IsDownloadInFinalState(
279    DownloadItem* download) {
280  return (download->GetState() == DownloadItem::IN_PROGRESS) &&
281      !download->GetTargetFilePath().empty();
282}
283
284DownloadTestObserverInterrupted::DownloadTestObserverInterrupted(
285    DownloadManager* download_manager,
286    size_t wait_count,
287    DangerousDownloadAction dangerous_download_action)
288        : DownloadTestObserver(download_manager,
289                               wait_count,
290                               dangerous_download_action) {
291  // You can't rely on overriden virtual functions in a base class constructor;
292  // the virtual function table hasn't been set up yet.  So, we have to do any
293  // work that depends on those functions in the derived class constructor
294  // instead.  In this case, it's because of |IsDownloadInFinalState()|.
295  Init();
296}
297
298DownloadTestObserverInterrupted::~DownloadTestObserverInterrupted() {
299}
300
301
302bool DownloadTestObserverInterrupted::IsDownloadInFinalState(
303    DownloadItem* download) {
304  return download->GetState() == DownloadItem::INTERRUPTED;
305}
306
307DownloadTestFlushObserver::DownloadTestFlushObserver(
308    DownloadManager* download_manager)
309    : download_manager_(download_manager),
310      waiting_for_zero_inprogress_(true) {}
311
312void DownloadTestFlushObserver::WaitForFlush() {
313  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
314  download_manager_->AddObserver(this);
315  // The wait condition may have been met before WaitForFlush() was called.
316  CheckDownloadsInProgress(true);
317  BrowserThread::GetBlockingPool()->FlushForTesting();
318  RunMessageLoop();
319}
320
321void DownloadTestFlushObserver::OnDownloadCreated(
322    DownloadManager* manager,
323    DownloadItem* item) {
324  CheckDownloadsInProgress(true);
325}
326
327void DownloadTestFlushObserver::OnDownloadDestroyed(DownloadItem* download) {
328  // Stop observing.  Do not do anything with it, as it is about to be gone.
329  DownloadSet::iterator it = downloads_observed_.find(download);
330  ASSERT_TRUE(it != downloads_observed_.end());
331  downloads_observed_.erase(it);
332  download->RemoveObserver(this);
333}
334
335void DownloadTestFlushObserver::OnDownloadUpdated(DownloadItem* download) {
336  // No change in DownloadItem set on manager.
337  CheckDownloadsInProgress(false);
338}
339
340DownloadTestFlushObserver::~DownloadTestFlushObserver() {
341  download_manager_->RemoveObserver(this);
342  for (DownloadSet::iterator it = downloads_observed_.begin();
343       it != downloads_observed_.end(); ++it) {
344    (*it)->RemoveObserver(this);
345  }
346}
347
348// If we're waiting for that flush point, check the number
349// of downloads in the IN_PROGRESS state and take appropriate
350// action.  If requested, also observes all downloads while iterating.
351void DownloadTestFlushObserver::CheckDownloadsInProgress(
352    bool observe_downloads) {
353  if (waiting_for_zero_inprogress_) {
354    int count = 0;
355
356    std::vector<DownloadItem*> downloads;
357    download_manager_->GetAllDownloads(&downloads);
358    for (std::vector<DownloadItem*>::iterator it = downloads.begin();
359         it != downloads.end(); ++it) {
360      if ((*it)->GetState() == DownloadItem::IN_PROGRESS)
361        count++;
362      if (observe_downloads) {
363        if (downloads_observed_.find(*it) == downloads_observed_.end()) {
364          (*it)->AddObserver(this);
365          downloads_observed_.insert(*it);
366        }
367        // Download items are forever, and we don't want to make
368        // assumptions about future state transitions, so once we
369        // start observing them, we don't stop until destruction.
370      }
371    }
372
373    if (count == 0) {
374      waiting_for_zero_inprogress_ = false;
375      // Stop observing DownloadItems.  We maintain the observation
376      // of DownloadManager so that we don't have to independently track
377      // whether we are observing it for conditional destruction.
378      for (DownloadSet::iterator it = downloads_observed_.begin();
379           it != downloads_observed_.end(); ++it) {
380        (*it)->RemoveObserver(this);
381      }
382      downloads_observed_.clear();
383
384      // Trigger next step.  We need to go past the IO thread twice, as
385      // there's a self-task posting in the IO thread cancel path.
386      BrowserThread::PostTask(
387          BrowserThread::FILE, FROM_HERE,
388          base::Bind(&DownloadTestFlushObserver::PingFileThread, this, 2));
389    }
390  }
391}
392
393void DownloadTestFlushObserver::PingFileThread(int cycle) {
394  BrowserThread::PostTask(
395      BrowserThread::IO, FROM_HERE,
396      base::Bind(&DownloadTestFlushObserver::PingIOThread, this, cycle));
397}
398
399void DownloadTestFlushObserver::PingIOThread(int cycle) {
400  if (--cycle) {
401    BrowserThread::PostTask(
402        BrowserThread::UI, FROM_HERE,
403        base::Bind(&DownloadTestFlushObserver::PingFileThread, this, cycle));
404  } else {
405    BrowserThread::PostTask(
406        BrowserThread::UI, FROM_HERE, base::MessageLoop::QuitClosure());
407  }
408}
409
410DownloadTestItemCreationObserver::DownloadTestItemCreationObserver()
411    : download_id_(DownloadItem::kInvalidId),
412      error_(net::OK),
413      called_back_count_(0),
414      waiting_(false) {
415}
416
417DownloadTestItemCreationObserver::~DownloadTestItemCreationObserver() {
418}
419
420void DownloadTestItemCreationObserver::WaitForDownloadItemCreation() {
421  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
422
423  if (called_back_count_ == 0) {
424    waiting_ = true;
425    RunMessageLoop();
426    waiting_ = false;
427  }
428}
429
430void DownloadTestItemCreationObserver::DownloadItemCreationCallback(
431    DownloadItem* item,
432    net::Error error) {
433  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
434
435  if (item)
436    download_id_ = item->GetId();
437  error_ = error;
438  ++called_back_count_;
439  DCHECK_EQ(1u, called_back_count_);
440
441  if (waiting_)
442    base::MessageLoopForUI::current()->Quit();
443}
444
445const DownloadUrlParameters::OnStartedCallback
446    DownloadTestItemCreationObserver::callback() {
447  return base::Bind(
448      &DownloadTestItemCreationObserver::DownloadItemCreationCallback, this);
449}
450
451}  // namespace content
452