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