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