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/test_file_error_injector.h" 6 7 #include <vector> 8 9 #include "base/compiler_specific.h" 10 #include "base/logging.h" 11 #include "content/browser/download/download_file_factory.h" 12 #include "content/browser/download/download_file_impl.h" 13 #include "content/browser/download/download_interrupt_reasons_impl.h" 14 #include "content/browser/download/download_manager_impl.h" 15 #include "content/browser/loader/resource_dispatcher_host_impl.h" 16 #include "content/public/browser/browser_thread.h" 17 #include "content/public/browser/power_save_blocker.h" 18 #include "url/gurl.h" 19 20 namespace content { 21 class ByteStreamReader; 22 23 namespace { 24 25 // A class that performs file operations and injects errors. 26 class DownloadFileWithErrors: public DownloadFileImpl { 27 public: 28 typedef base::Callback<void(const GURL& url)> ConstructionCallback; 29 typedef base::Callback<void(const GURL& url)> DestructionCallback; 30 31 DownloadFileWithErrors( 32 scoped_ptr<DownloadSaveInfo> save_info, 33 const base::FilePath& default_download_directory, 34 const GURL& url, 35 const GURL& referrer_url, 36 bool calculate_hash, 37 scoped_ptr<ByteStreamReader> stream, 38 const net::BoundNetLog& bound_net_log, 39 scoped_ptr<PowerSaveBlocker> power_save_blocker, 40 base::WeakPtr<DownloadDestinationObserver> observer, 41 const TestFileErrorInjector::FileErrorInfo& error_info, 42 const ConstructionCallback& ctor_callback, 43 const DestructionCallback& dtor_callback); 44 45 virtual ~DownloadFileWithErrors(); 46 47 virtual void Initialize(const InitializeCallback& callback) OVERRIDE; 48 49 // DownloadFile interface. 50 virtual DownloadInterruptReason AppendDataToFile( 51 const char* data, size_t data_len) OVERRIDE; 52 virtual void RenameAndUniquify( 53 const base::FilePath& full_path, 54 const RenameCompletionCallback& callback) OVERRIDE; 55 virtual void RenameAndAnnotate( 56 const base::FilePath& full_path, 57 const RenameCompletionCallback& callback) OVERRIDE; 58 59 private: 60 // Error generating helper. 61 DownloadInterruptReason ShouldReturnError( 62 TestFileErrorInjector::FileOperationCode code, 63 DownloadInterruptReason original_error); 64 65 // Determine whether to overwrite an operation with the given code 66 // with a substitute error; if returns true, |*original_error| is 67 // written with the error to use for overwriting. 68 // NOTE: This routine changes state; specifically, it increases the 69 // operations counts for the specified code. It should only be called 70 // once per operation. 71 bool OverwriteError( 72 TestFileErrorInjector::FileOperationCode code, 73 DownloadInterruptReason* output_error); 74 75 // Source URL for the file being downloaded. 76 GURL source_url_; 77 78 // Our injected error. Only one per file. 79 TestFileErrorInjector::FileErrorInfo error_info_; 80 81 // Count per operation. 0-based. 82 std::map<TestFileErrorInjector::FileOperationCode, int> operation_counter_; 83 84 // Callback for destruction. 85 DestructionCallback destruction_callback_; 86 }; 87 88 static void InitializeErrorCallback( 89 const DownloadFile::InitializeCallback original_callback, 90 DownloadInterruptReason overwrite_error, 91 DownloadInterruptReason original_error) { 92 original_callback.Run(overwrite_error); 93 } 94 95 static void RenameErrorCallback( 96 const DownloadFile::RenameCompletionCallback original_callback, 97 DownloadInterruptReason overwrite_error, 98 DownloadInterruptReason original_error, 99 const base::FilePath& path_result) { 100 original_callback.Run( 101 overwrite_error, 102 overwrite_error == DOWNLOAD_INTERRUPT_REASON_NONE ? 103 path_result : base::FilePath()); 104 } 105 106 DownloadFileWithErrors::DownloadFileWithErrors( 107 scoped_ptr<DownloadSaveInfo> save_info, 108 const base::FilePath& default_download_directory, 109 const GURL& url, 110 const GURL& referrer_url, 111 bool calculate_hash, 112 scoped_ptr<ByteStreamReader> stream, 113 const net::BoundNetLog& bound_net_log, 114 scoped_ptr<PowerSaveBlocker> power_save_blocker, 115 base::WeakPtr<DownloadDestinationObserver> observer, 116 const TestFileErrorInjector::FileErrorInfo& error_info, 117 const ConstructionCallback& ctor_callback, 118 const DestructionCallback& dtor_callback) 119 : DownloadFileImpl( 120 save_info.Pass(), default_download_directory, url, referrer_url, 121 calculate_hash, stream.Pass(), bound_net_log, 122 power_save_blocker.Pass(), observer), 123 source_url_(url), 124 error_info_(error_info), 125 destruction_callback_(dtor_callback) { 126 // DownloadFiles are created on the UI thread and are destroyed on the FILE 127 // thread. Schedule the ConstructionCallback on the FILE thread so that if a 128 // DownloadItem schedules a DownloadFile to be destroyed and creates another 129 // one (as happens during download resumption), then the DestructionCallback 130 // for the old DownloadFile is run before the ConstructionCallback for the 131 // next DownloadFile. 132 BrowserThread::PostTask( 133 BrowserThread::FILE, 134 FROM_HERE, 135 base::Bind(ctor_callback, source_url_)); 136 } 137 138 DownloadFileWithErrors::~DownloadFileWithErrors() { 139 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 140 destruction_callback_.Run(source_url_); 141 } 142 143 void DownloadFileWithErrors::Initialize( 144 const InitializeCallback& callback) { 145 DownloadInterruptReason error_to_return = DOWNLOAD_INTERRUPT_REASON_NONE; 146 InitializeCallback callback_to_use = callback; 147 148 // Replace callback if the error needs to be overwritten. 149 if (OverwriteError( 150 TestFileErrorInjector::FILE_OPERATION_INITIALIZE, 151 &error_to_return)) { 152 if (DOWNLOAD_INTERRUPT_REASON_NONE != error_to_return) { 153 // Don't execute a, probably successful, Initialize; just 154 // return the error. 155 BrowserThread::PostTask( 156 BrowserThread::UI, FROM_HERE, base::Bind( 157 callback, error_to_return)); 158 return; 159 } 160 161 // Otherwise, just wrap the return. 162 callback_to_use = base::Bind(&InitializeErrorCallback, callback, 163 error_to_return); 164 } 165 166 DownloadFileImpl::Initialize(callback_to_use); 167 } 168 169 DownloadInterruptReason DownloadFileWithErrors::AppendDataToFile( 170 const char* data, size_t data_len) { 171 return ShouldReturnError( 172 TestFileErrorInjector::FILE_OPERATION_WRITE, 173 DownloadFileImpl::AppendDataToFile(data, data_len)); 174 } 175 176 void DownloadFileWithErrors::RenameAndUniquify( 177 const base::FilePath& full_path, 178 const RenameCompletionCallback& callback) { 179 DownloadInterruptReason error_to_return = DOWNLOAD_INTERRUPT_REASON_NONE; 180 RenameCompletionCallback callback_to_use = callback; 181 182 // Replace callback if the error needs to be overwritten. 183 if (OverwriteError( 184 TestFileErrorInjector::FILE_OPERATION_RENAME_UNIQUIFY, 185 &error_to_return)) { 186 if (DOWNLOAD_INTERRUPT_REASON_NONE != error_to_return) { 187 // Don't execute a, probably successful, RenameAndUniquify; just 188 // return the error. 189 BrowserThread::PostTask( 190 BrowserThread::UI, FROM_HERE, base::Bind( 191 callback, error_to_return, base::FilePath())); 192 return; 193 } 194 195 // Otherwise, just wrap the return. 196 callback_to_use = base::Bind(&RenameErrorCallback, callback, 197 error_to_return); 198 } 199 200 DownloadFileImpl::RenameAndUniquify(full_path, callback_to_use); 201 } 202 203 void DownloadFileWithErrors::RenameAndAnnotate( 204 const base::FilePath& full_path, 205 const RenameCompletionCallback& callback) { 206 DownloadInterruptReason error_to_return = DOWNLOAD_INTERRUPT_REASON_NONE; 207 RenameCompletionCallback callback_to_use = callback; 208 209 // Replace callback if the error needs to be overwritten. 210 if (OverwriteError( 211 TestFileErrorInjector::FILE_OPERATION_RENAME_ANNOTATE, 212 &error_to_return)) { 213 if (DOWNLOAD_INTERRUPT_REASON_NONE != error_to_return) { 214 // Don't execute a, probably successful, RenameAndAnnotate; just 215 // return the error. 216 BrowserThread::PostTask( 217 BrowserThread::UI, FROM_HERE, base::Bind( 218 callback, error_to_return, base::FilePath())); 219 return; 220 } 221 222 // Otherwise, just wrap the return. 223 callback_to_use = base::Bind(&RenameErrorCallback, callback, 224 error_to_return); 225 } 226 227 DownloadFileImpl::RenameAndAnnotate(full_path, callback_to_use); 228 } 229 230 bool DownloadFileWithErrors::OverwriteError( 231 TestFileErrorInjector::FileOperationCode code, 232 DownloadInterruptReason* output_error) { 233 int counter = operation_counter_[code]++; 234 235 if (code != error_info_.code) 236 return false; 237 238 if (counter != error_info_.operation_instance) 239 return false; 240 241 *output_error = error_info_.error; 242 return true; 243 } 244 245 DownloadInterruptReason DownloadFileWithErrors::ShouldReturnError( 246 TestFileErrorInjector::FileOperationCode code, 247 DownloadInterruptReason original_error) { 248 DownloadInterruptReason output_error = original_error; 249 OverwriteError(code, &output_error); 250 return output_error; 251 } 252 253 } // namespace 254 255 // A factory for constructing DownloadFiles that inject errors. 256 class DownloadFileWithErrorsFactory : public DownloadFileFactory { 257 public: 258 DownloadFileWithErrorsFactory( 259 const DownloadFileWithErrors::ConstructionCallback& ctor_callback, 260 const DownloadFileWithErrors::DestructionCallback& dtor_callback); 261 virtual ~DownloadFileWithErrorsFactory(); 262 263 // DownloadFileFactory interface. 264 virtual DownloadFile* CreateFile( 265 scoped_ptr<DownloadSaveInfo> save_info, 266 const base::FilePath& default_download_directory, 267 const GURL& url, 268 const GURL& referrer_url, 269 bool calculate_hash, 270 scoped_ptr<ByteStreamReader> stream, 271 const net::BoundNetLog& bound_net_log, 272 base::WeakPtr<DownloadDestinationObserver> observer) OVERRIDE; 273 274 bool AddError( 275 const TestFileErrorInjector::FileErrorInfo& error_info); 276 277 void ClearErrors(); 278 279 private: 280 // Our injected error list, mapped by URL. One per file. 281 TestFileErrorInjector::ErrorMap injected_errors_; 282 283 // Callback for creation and destruction. 284 DownloadFileWithErrors::ConstructionCallback construction_callback_; 285 DownloadFileWithErrors::DestructionCallback destruction_callback_; 286 }; 287 288 DownloadFileWithErrorsFactory::DownloadFileWithErrorsFactory( 289 const DownloadFileWithErrors::ConstructionCallback& ctor_callback, 290 const DownloadFileWithErrors::DestructionCallback& dtor_callback) 291 : construction_callback_(ctor_callback), 292 destruction_callback_(dtor_callback) { 293 } 294 295 DownloadFileWithErrorsFactory::~DownloadFileWithErrorsFactory() { 296 } 297 298 DownloadFile* DownloadFileWithErrorsFactory::CreateFile( 299 scoped_ptr<DownloadSaveInfo> save_info, 300 const base::FilePath& default_download_directory, 301 const GURL& url, 302 const GURL& referrer_url, 303 bool calculate_hash, 304 scoped_ptr<ByteStreamReader> stream, 305 const net::BoundNetLog& bound_net_log, 306 base::WeakPtr<DownloadDestinationObserver> observer) { 307 if (injected_errors_.find(url.spec()) == injected_errors_.end()) { 308 // Have to create entry, because FileErrorInfo is not a POD type. 309 TestFileErrorInjector::FileErrorInfo err_info = { 310 url.spec(), 311 TestFileErrorInjector::FILE_OPERATION_INITIALIZE, 312 -1, 313 DOWNLOAD_INTERRUPT_REASON_NONE 314 }; 315 injected_errors_[url.spec()] = err_info; 316 } 317 318 scoped_ptr<PowerSaveBlocker> psb( 319 PowerSaveBlocker::Create( 320 PowerSaveBlocker::kPowerSaveBlockPreventAppSuspension, 321 "Download in progress")); 322 323 return new DownloadFileWithErrors( 324 save_info.Pass(), 325 default_download_directory, 326 url, 327 referrer_url, 328 calculate_hash, 329 stream.Pass(), 330 bound_net_log, 331 psb.Pass(), 332 observer, 333 injected_errors_[url.spec()], 334 construction_callback_, 335 destruction_callback_); 336 } 337 338 bool DownloadFileWithErrorsFactory::AddError( 339 const TestFileErrorInjector::FileErrorInfo& error_info) { 340 // Creates an empty entry if necessary. Duplicate entries overwrite. 341 injected_errors_[error_info.url] = error_info; 342 343 return true; 344 } 345 346 void DownloadFileWithErrorsFactory::ClearErrors() { 347 injected_errors_.clear(); 348 } 349 350 TestFileErrorInjector::TestFileErrorInjector( 351 DownloadManager* download_manager) 352 : created_factory_(NULL), 353 // This code is only used for browser_tests, so a 354 // DownloadManager is always a DownloadManagerImpl. 355 download_manager_(static_cast<DownloadManagerImpl*>(download_manager)) { 356 // Record the value of the pointer, for later validation. 357 created_factory_ = 358 new DownloadFileWithErrorsFactory( 359 base::Bind(&TestFileErrorInjector::RecordDownloadFileConstruction, 360 this), 361 base::Bind(&TestFileErrorInjector::RecordDownloadFileDestruction, 362 this)); 363 364 // We will transfer ownership of the factory to the download manager. 365 scoped_ptr<DownloadFileFactory> download_file_factory( 366 created_factory_); 367 368 download_manager_->SetDownloadFileFactoryForTesting( 369 download_file_factory.Pass()); 370 } 371 372 TestFileErrorInjector::~TestFileErrorInjector() { 373 } 374 375 bool TestFileErrorInjector::AddError(const FileErrorInfo& error_info) { 376 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 377 DCHECK_LE(0, error_info.operation_instance); 378 DCHECK(injected_errors_.find(error_info.url) == injected_errors_.end()); 379 380 // Creates an empty entry if necessary. 381 injected_errors_[error_info.url] = error_info; 382 383 return true; 384 } 385 386 void TestFileErrorInjector::ClearErrors() { 387 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 388 injected_errors_.clear(); 389 } 390 391 bool TestFileErrorInjector::InjectErrors() { 392 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 393 394 ClearFoundFiles(); 395 396 DCHECK_EQ(static_cast<DownloadFileFactory*>(created_factory_), 397 download_manager_->GetDownloadFileFactoryForTesting()); 398 399 created_factory_->ClearErrors(); 400 401 for (ErrorMap::const_iterator it = injected_errors_.begin(); 402 it != injected_errors_.end(); ++it) 403 created_factory_->AddError(it->second); 404 405 return true; 406 } 407 408 size_t TestFileErrorInjector::CurrentFileCount() const { 409 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 410 return files_.size(); 411 } 412 413 size_t TestFileErrorInjector::TotalFileCount() const { 414 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 415 return found_files_.size(); 416 } 417 418 419 bool TestFileErrorInjector::HadFile(const GURL& url) const { 420 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 421 422 return (found_files_.find(url) != found_files_.end()); 423 } 424 425 void TestFileErrorInjector::ClearFoundFiles() { 426 found_files_.clear(); 427 } 428 429 void TestFileErrorInjector::DownloadFileCreated(GURL url) { 430 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 431 DCHECK(files_.find(url) == files_.end()); 432 433 files_.insert(url); 434 found_files_.insert(url); 435 } 436 437 void TestFileErrorInjector::DestroyingDownloadFile(GURL url) { 438 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 439 DCHECK(files_.find(url) != files_.end()); 440 441 files_.erase(url); 442 } 443 444 void TestFileErrorInjector::RecordDownloadFileConstruction(const GURL& url) { 445 BrowserThread::PostTask( 446 BrowserThread::UI, 447 FROM_HERE, 448 base::Bind(&TestFileErrorInjector::DownloadFileCreated, this, url)); 449 } 450 451 void TestFileErrorInjector::RecordDownloadFileDestruction(const GURL& url) { 452 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, 453 base::Bind(&TestFileErrorInjector::DestroyingDownloadFile, this, url)); 454 } 455 456 // static 457 scoped_refptr<TestFileErrorInjector> TestFileErrorInjector::Create( 458 DownloadManager* download_manager) { 459 static bool visited = false; 460 DCHECK(!visited); // Only allowed to be called once. 461 visited = true; 462 463 scoped_refptr<TestFileErrorInjector> single_injector( 464 new TestFileErrorInjector(download_manager)); 465 466 return single_injector; 467 } 468 469 // static 470 std::string TestFileErrorInjector::DebugString(FileOperationCode code) { 471 switch (code) { 472 case FILE_OPERATION_INITIALIZE: 473 return "INITIALIZE"; 474 case FILE_OPERATION_WRITE: 475 return "WRITE"; 476 case FILE_OPERATION_RENAME_UNIQUIFY: 477 return "RENAME_UNIQUIFY"; 478 case FILE_OPERATION_RENAME_ANNOTATE: 479 return "RENAME_ANNOTATE"; 480 default: 481 break; 482 } 483 484 return "Unknown"; 485 } 486 487 } // namespace content 488