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