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