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
19 namespace content {
20
DownloadUpdatedObserver(DownloadItem * item,DownloadUpdatedObserver::EventFilter filter)21 DownloadUpdatedObserver::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
~DownloadUpdatedObserver()30 DownloadUpdatedObserver::~DownloadUpdatedObserver() {
31 if (item_)
32 item_->RemoveObserver(this);
33 }
34
WaitForEvent()35 bool 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
OnDownloadUpdated(DownloadItem * item)47 void 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
OnDownloadDestroyed(DownloadItem * item)55 void 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
DownloadTestObserver(DownloadManager * download_manager,size_t wait_count,DangerousDownloadAction dangerous_download_action)63 DownloadTestObserver::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
~DownloadTestObserver()75 DownloadTestObserver::~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
Init()84 void 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
ManagerGoingDown(DownloadManager * manager)96 void DownloadTestObserver::ManagerGoingDown(DownloadManager* manager) {
97 CHECK_EQ(manager, download_manager_);
98 download_manager_ = NULL;
99 SignalIfFinished();
100 }
101
WaitForFinished()102 void DownloadTestObserver::WaitForFinished() {
103 if (!IsFinished()) {
104 waiting_ = true;
105 RunMessageLoop();
106 waiting_ = false;
107 }
108 }
109
IsFinished() const110 bool DownloadTestObserver::IsFinished() const {
111 return (finished_downloads_.size() - finished_downloads_at_construction_ >=
112 wait_count_) || (download_manager_ == NULL);
113 }
114
OnDownloadCreated(DownloadManager * manager,DownloadItem * item)115 void 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
OnDownloadDestroyed(DownloadItem * download)130 void 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
OnDownloadUpdated(DownloadItem * download)138 void 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 case ON_DANGEROUS_DOWNLOAD_QUIT:
176 DownloadInFinalState(download);
177 break;
178
179 default:
180 NOTREACHED();
181 }
182 }
183
184 if (IsDownloadInFinalState(download))
185 DownloadInFinalState(download);
186 }
187
NumDangerousDownloadsSeen() const188 size_t DownloadTestObserver::NumDangerousDownloadsSeen() const {
189 return dangerous_downloads_seen_.size();
190 }
191
NumDownloadsSeenInState(DownloadItem::DownloadState state) const192 size_t DownloadTestObserver::NumDownloadsSeenInState(
193 DownloadItem::DownloadState state) const {
194 StateMap::const_iterator it = states_observed_.find(state);
195
196 if (it == states_observed_.end())
197 return 0;
198
199 return it->second;
200 }
201
DownloadInFinalState(DownloadItem * download)202 void DownloadTestObserver::DownloadInFinalState(DownloadItem* download) {
203 if (finished_downloads_.find(download) != finished_downloads_.end()) {
204 // We've already seen the final state on this download.
205 return;
206 }
207
208 // Record the transition.
209 finished_downloads_.insert(download);
210
211 // Record the state.
212 states_observed_[download->GetState()]++; // Initializes to 0 the first time.
213
214 SignalIfFinished();
215 }
216
SignalIfFinished()217 void DownloadTestObserver::SignalIfFinished() {
218 if (waiting_ && IsFinished())
219 base::MessageLoopForUI::current()->Quit();
220 }
221
AcceptDangerousDownload(uint32 download_id)222 void DownloadTestObserver::AcceptDangerousDownload(uint32 download_id) {
223 // Download manager was shutdown before the UI thread could accept the
224 // download.
225 if (!download_manager_)
226 return;
227 DownloadItem* download = download_manager_->GetDownload(download_id);
228 if (download && !download->IsDone())
229 download->ValidateDangerousDownload();
230 }
231
DenyDangerousDownload(uint32 download_id)232 void DownloadTestObserver::DenyDangerousDownload(uint32 download_id) {
233 // Download manager was shutdown before the UI thread could deny the
234 // download.
235 if (!download_manager_)
236 return;
237 DownloadItem* download = download_manager_->GetDownload(download_id);
238 if (download && !download->IsDone())
239 download->Remove();
240 }
241
DownloadTestObserverTerminal(DownloadManager * download_manager,size_t wait_count,DangerousDownloadAction dangerous_download_action)242 DownloadTestObserverTerminal::DownloadTestObserverTerminal(
243 DownloadManager* download_manager,
244 size_t wait_count,
245 DangerousDownloadAction dangerous_download_action)
246 : DownloadTestObserver(download_manager,
247 wait_count,
248 dangerous_download_action) {
249 // You can't rely on overriden virtual functions in a base class constructor;
250 // the virtual function table hasn't been set up yet. So, we have to do any
251 // work that depends on those functions in the derived class constructor
252 // instead. In this case, it's because of |IsDownloadInFinalState()|.
253 Init();
254 }
255
~DownloadTestObserverTerminal()256 DownloadTestObserverTerminal::~DownloadTestObserverTerminal() {
257 }
258
259
IsDownloadInFinalState(DownloadItem * download)260 bool DownloadTestObserverTerminal::IsDownloadInFinalState(
261 DownloadItem* download) {
262 return download->IsDone();
263 }
264
DownloadTestObserverInProgress(DownloadManager * download_manager,size_t wait_count)265 DownloadTestObserverInProgress::DownloadTestObserverInProgress(
266 DownloadManager* download_manager,
267 size_t wait_count)
268 : DownloadTestObserver(download_manager,
269 wait_count,
270 ON_DANGEROUS_DOWNLOAD_ACCEPT) {
271 // You can't override virtual functions in a base class constructor; the
272 // virtual function table hasn't been set up yet. So, we have to do any
273 // work that depends on those functions in the derived class constructor
274 // instead. In this case, it's because of |IsDownloadInFinalState()|.
275 Init();
276 }
277
~DownloadTestObserverInProgress()278 DownloadTestObserverInProgress::~DownloadTestObserverInProgress() {
279 }
280
281
IsDownloadInFinalState(DownloadItem * download)282 bool DownloadTestObserverInProgress::IsDownloadInFinalState(
283 DownloadItem* download) {
284 return (download->GetState() == DownloadItem::IN_PROGRESS) &&
285 !download->GetTargetFilePath().empty();
286 }
287
DownloadTestObserverInterrupted(DownloadManager * download_manager,size_t wait_count,DangerousDownloadAction dangerous_download_action)288 DownloadTestObserverInterrupted::DownloadTestObserverInterrupted(
289 DownloadManager* download_manager,
290 size_t wait_count,
291 DangerousDownloadAction dangerous_download_action)
292 : DownloadTestObserver(download_manager,
293 wait_count,
294 dangerous_download_action) {
295 // You can't rely on overriden virtual functions in a base class constructor;
296 // the virtual function table hasn't been set up yet. So, we have to do any
297 // work that depends on those functions in the derived class constructor
298 // instead. In this case, it's because of |IsDownloadInFinalState()|.
299 Init();
300 }
301
~DownloadTestObserverInterrupted()302 DownloadTestObserverInterrupted::~DownloadTestObserverInterrupted() {
303 }
304
305
IsDownloadInFinalState(DownloadItem * download)306 bool DownloadTestObserverInterrupted::IsDownloadInFinalState(
307 DownloadItem* download) {
308 return download->GetState() == DownloadItem::INTERRUPTED;
309 }
310
DownloadTestFlushObserver(DownloadManager * download_manager)311 DownloadTestFlushObserver::DownloadTestFlushObserver(
312 DownloadManager* download_manager)
313 : download_manager_(download_manager),
314 waiting_for_zero_inprogress_(true) {}
315
WaitForFlush()316 void DownloadTestFlushObserver::WaitForFlush() {
317 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
318 download_manager_->AddObserver(this);
319 // The wait condition may have been met before WaitForFlush() was called.
320 CheckDownloadsInProgress(true);
321 BrowserThread::GetBlockingPool()->FlushForTesting();
322 RunMessageLoop();
323 }
324
OnDownloadCreated(DownloadManager * manager,DownloadItem * item)325 void DownloadTestFlushObserver::OnDownloadCreated(
326 DownloadManager* manager,
327 DownloadItem* item) {
328 CheckDownloadsInProgress(true);
329 }
330
OnDownloadDestroyed(DownloadItem * download)331 void DownloadTestFlushObserver::OnDownloadDestroyed(DownloadItem* download) {
332 // Stop observing. Do not do anything with it, as it is about to be gone.
333 DownloadSet::iterator it = downloads_observed_.find(download);
334 ASSERT_TRUE(it != downloads_observed_.end());
335 downloads_observed_.erase(it);
336 download->RemoveObserver(this);
337 }
338
OnDownloadUpdated(DownloadItem * download)339 void DownloadTestFlushObserver::OnDownloadUpdated(DownloadItem* download) {
340 // No change in DownloadItem set on manager.
341 CheckDownloadsInProgress(false);
342 }
343
~DownloadTestFlushObserver()344 DownloadTestFlushObserver::~DownloadTestFlushObserver() {
345 download_manager_->RemoveObserver(this);
346 for (DownloadSet::iterator it = downloads_observed_.begin();
347 it != downloads_observed_.end(); ++it) {
348 (*it)->RemoveObserver(this);
349 }
350 }
351
352 // If we're waiting for that flush point, check the number
353 // of downloads in the IN_PROGRESS state and take appropriate
354 // action. If requested, also observes all downloads while iterating.
CheckDownloadsInProgress(bool observe_downloads)355 void DownloadTestFlushObserver::CheckDownloadsInProgress(
356 bool observe_downloads) {
357 if (waiting_for_zero_inprogress_) {
358 int count = 0;
359
360 std::vector<DownloadItem*> downloads;
361 download_manager_->GetAllDownloads(&downloads);
362 for (std::vector<DownloadItem*>::iterator it = downloads.begin();
363 it != downloads.end(); ++it) {
364 if ((*it)->GetState() == DownloadItem::IN_PROGRESS)
365 count++;
366 if (observe_downloads) {
367 if (downloads_observed_.find(*it) == downloads_observed_.end()) {
368 (*it)->AddObserver(this);
369 downloads_observed_.insert(*it);
370 }
371 // Download items are forever, and we don't want to make
372 // assumptions about future state transitions, so once we
373 // start observing them, we don't stop until destruction.
374 }
375 }
376
377 if (count == 0) {
378 waiting_for_zero_inprogress_ = false;
379 // Stop observing DownloadItems. We maintain the observation
380 // of DownloadManager so that we don't have to independently track
381 // whether we are observing it for conditional destruction.
382 for (DownloadSet::iterator it = downloads_observed_.begin();
383 it != downloads_observed_.end(); ++it) {
384 (*it)->RemoveObserver(this);
385 }
386 downloads_observed_.clear();
387
388 // Trigger next step. We need to go past the IO thread twice, as
389 // there's a self-task posting in the IO thread cancel path.
390 BrowserThread::PostTask(
391 BrowserThread::FILE, FROM_HERE,
392 base::Bind(&DownloadTestFlushObserver::PingFileThread, this, 2));
393 }
394 }
395 }
396
PingFileThread(int cycle)397 void DownloadTestFlushObserver::PingFileThread(int cycle) {
398 BrowserThread::PostTask(
399 BrowserThread::IO, FROM_HERE,
400 base::Bind(&DownloadTestFlushObserver::PingIOThread, this, cycle));
401 }
402
PingIOThread(int cycle)403 void DownloadTestFlushObserver::PingIOThread(int cycle) {
404 if (--cycle) {
405 BrowserThread::PostTask(
406 BrowserThread::UI, FROM_HERE,
407 base::Bind(&DownloadTestFlushObserver::PingFileThread, this, cycle));
408 } else {
409 BrowserThread::PostTask(
410 BrowserThread::UI, FROM_HERE, base::MessageLoop::QuitClosure());
411 }
412 }
413
DownloadTestItemCreationObserver()414 DownloadTestItemCreationObserver::DownloadTestItemCreationObserver()
415 : download_id_(DownloadItem::kInvalidId),
416 interrupt_reason_(DOWNLOAD_INTERRUPT_REASON_NONE),
417 called_back_count_(0),
418 waiting_(false) {
419 }
420
~DownloadTestItemCreationObserver()421 DownloadTestItemCreationObserver::~DownloadTestItemCreationObserver() {
422 }
423
WaitForDownloadItemCreation()424 void DownloadTestItemCreationObserver::WaitForDownloadItemCreation() {
425 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
426
427 if (called_back_count_ == 0) {
428 waiting_ = true;
429 RunMessageLoop();
430 waiting_ = false;
431 }
432 }
433
DownloadItemCreationCallback(DownloadItem * item,DownloadInterruptReason interrupt_reason)434 void DownloadTestItemCreationObserver::DownloadItemCreationCallback(
435 DownloadItem* item,
436 DownloadInterruptReason interrupt_reason) {
437 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
438
439 if (item)
440 download_id_ = item->GetId();
441 interrupt_reason_ = interrupt_reason;
442 ++called_back_count_;
443 DCHECK_EQ(1u, called_back_count_);
444
445 if (waiting_)
446 base::MessageLoopForUI::current()->Quit();
447 }
448
449 const DownloadUrlParameters::OnStartedCallback
callback()450 DownloadTestItemCreationObserver::callback() {
451 return base::Bind(
452 &DownloadTestItemCreationObserver::DownloadItemCreationCallback, this);
453 }
454
455 } // namespace content
456