test_file_error_injector.cc revision 90dce4d38c5ff5333bea97d859d4e484e27edf0c
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 "googleurl/src/gurl.h" 19 20namespace content { 21class ByteStreamReader; 22 23namespace { 24 25// A class that performs file operations and injects errors. 26class 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 88static void InitializeErrorCallback( 89 const DownloadFile::InitializeCallback original_callback, 90 DownloadInterruptReason overwrite_error, 91 DownloadInterruptReason original_error) { 92 original_callback.Run(overwrite_error); 93} 94 95static 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 106DownloadFileWithErrors::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 ctor_callback.Run(source_url_); 127} 128 129DownloadFileWithErrors::~DownloadFileWithErrors() { 130 destruction_callback_.Run(source_url_); 131} 132 133void DownloadFileWithErrors::Initialize( 134 const InitializeCallback& callback) { 135 DownloadInterruptReason error_to_return = DOWNLOAD_INTERRUPT_REASON_NONE; 136 InitializeCallback callback_to_use = callback; 137 138 // Replace callback if the error needs to be overwritten. 139 if (OverwriteError( 140 TestFileErrorInjector::FILE_OPERATION_INITIALIZE, 141 &error_to_return)) { 142 if (DOWNLOAD_INTERRUPT_REASON_NONE != error_to_return) { 143 // Don't execute a, probably successful, Initialize; just 144 // return the error. 145 BrowserThread::PostTask( 146 BrowserThread::UI, FROM_HERE, base::Bind( 147 callback, error_to_return)); 148 return; 149 } 150 151 // Otherwise, just wrap the return. 152 callback_to_use = base::Bind(&InitializeErrorCallback, callback, 153 error_to_return); 154 } 155 156 DownloadFileImpl::Initialize(callback_to_use); 157} 158 159DownloadInterruptReason DownloadFileWithErrors::AppendDataToFile( 160 const char* data, size_t data_len) { 161 return ShouldReturnError( 162 TestFileErrorInjector::FILE_OPERATION_WRITE, 163 DownloadFileImpl::AppendDataToFile(data, data_len)); 164} 165 166void DownloadFileWithErrors::RenameAndUniquify( 167 const base::FilePath& full_path, 168 const RenameCompletionCallback& callback) { 169 DownloadInterruptReason error_to_return = DOWNLOAD_INTERRUPT_REASON_NONE; 170 RenameCompletionCallback callback_to_use = callback; 171 172 // Replace callback if the error needs to be overwritten. 173 if (OverwriteError( 174 TestFileErrorInjector::FILE_OPERATION_RENAME_UNIQUIFY, 175 &error_to_return)) { 176 if (DOWNLOAD_INTERRUPT_REASON_NONE != error_to_return) { 177 // Don't execute a, probably successful, RenameAndUniquify; just 178 // return the error. 179 BrowserThread::PostTask( 180 BrowserThread::UI, FROM_HERE, base::Bind( 181 callback, error_to_return, base::FilePath())); 182 return; 183 } 184 185 // Otherwise, just wrap the return. 186 callback_to_use = base::Bind(&RenameErrorCallback, callback, 187 error_to_return); 188 } 189 190 DownloadFileImpl::RenameAndUniquify(full_path, callback_to_use); 191} 192 193void DownloadFileWithErrors::RenameAndAnnotate( 194 const base::FilePath& full_path, 195 const RenameCompletionCallback& callback) { 196 DownloadInterruptReason error_to_return = DOWNLOAD_INTERRUPT_REASON_NONE; 197 RenameCompletionCallback callback_to_use = callback; 198 199 // Replace callback if the error needs to be overwritten. 200 if (OverwriteError( 201 TestFileErrorInjector::FILE_OPERATION_RENAME_ANNOTATE, 202 &error_to_return)) { 203 if (DOWNLOAD_INTERRUPT_REASON_NONE != error_to_return) { 204 // Don't execute a, probably successful, RenameAndAnnotate; just 205 // return the error. 206 BrowserThread::PostTask( 207 BrowserThread::UI, FROM_HERE, base::Bind( 208 callback, error_to_return, base::FilePath())); 209 return; 210 } 211 212 // Otherwise, just wrap the return. 213 callback_to_use = base::Bind(&RenameErrorCallback, callback, 214 error_to_return); 215 } 216 217 DownloadFileImpl::RenameAndAnnotate(full_path, callback_to_use); 218} 219 220bool DownloadFileWithErrors::OverwriteError( 221 TestFileErrorInjector::FileOperationCode code, 222 DownloadInterruptReason* output_error) { 223 int counter = operation_counter_[code]++; 224 225 if (code != error_info_.code) 226 return false; 227 228 if (counter != error_info_.operation_instance) 229 return false; 230 231 *output_error = error_info_.error; 232 return true; 233} 234 235DownloadInterruptReason DownloadFileWithErrors::ShouldReturnError( 236 TestFileErrorInjector::FileOperationCode code, 237 DownloadInterruptReason original_error) { 238 DownloadInterruptReason output_error = original_error; 239 OverwriteError(code, &output_error); 240 return output_error; 241} 242 243} // namespace 244 245// A factory for constructing DownloadFiles that inject errors. 246class DownloadFileWithErrorsFactory : public DownloadFileFactory { 247 public: 248 DownloadFileWithErrorsFactory( 249 const DownloadFileWithErrors::ConstructionCallback& ctor_callback, 250 const DownloadFileWithErrors::DestructionCallback& dtor_callback); 251 virtual ~DownloadFileWithErrorsFactory(); 252 253 // DownloadFileFactory interface. 254 virtual DownloadFile* CreateFile( 255 scoped_ptr<DownloadSaveInfo> save_info, 256 const base::FilePath& default_download_directory, 257 const GURL& url, 258 const GURL& referrer_url, 259 bool calculate_hash, 260 scoped_ptr<ByteStreamReader> stream, 261 const net::BoundNetLog& bound_net_log, 262 base::WeakPtr<DownloadDestinationObserver> observer) OVERRIDE; 263 264 bool AddError( 265 const TestFileErrorInjector::FileErrorInfo& error_info); 266 267 void ClearErrors(); 268 269 private: 270 // Our injected error list, mapped by URL. One per file. 271 TestFileErrorInjector::ErrorMap injected_errors_; 272 273 // Callback for creation and destruction. 274 DownloadFileWithErrors::ConstructionCallback construction_callback_; 275 DownloadFileWithErrors::DestructionCallback destruction_callback_; 276}; 277 278DownloadFileWithErrorsFactory::DownloadFileWithErrorsFactory( 279 const DownloadFileWithErrors::ConstructionCallback& ctor_callback, 280 const DownloadFileWithErrors::DestructionCallback& dtor_callback) 281 : construction_callback_(ctor_callback), 282 destruction_callback_(dtor_callback) { 283} 284 285DownloadFileWithErrorsFactory::~DownloadFileWithErrorsFactory() { 286} 287 288DownloadFile* DownloadFileWithErrorsFactory::CreateFile( 289 scoped_ptr<DownloadSaveInfo> save_info, 290 const base::FilePath& default_download_directory, 291 const GURL& url, 292 const GURL& referrer_url, 293 bool calculate_hash, 294 scoped_ptr<ByteStreamReader> stream, 295 const net::BoundNetLog& bound_net_log, 296 base::WeakPtr<DownloadDestinationObserver> observer) { 297 if (injected_errors_.find(url.spec()) == injected_errors_.end()) { 298 // Have to create entry, because FileErrorInfo is not a POD type. 299 TestFileErrorInjector::FileErrorInfo err_info = { 300 url.spec(), 301 TestFileErrorInjector::FILE_OPERATION_INITIALIZE, 302 -1, 303 DOWNLOAD_INTERRUPT_REASON_NONE 304 }; 305 injected_errors_[url.spec()] = err_info; 306 } 307 308 scoped_ptr<PowerSaveBlocker> psb( 309 PowerSaveBlocker::Create( 310 PowerSaveBlocker::kPowerSaveBlockPreventAppSuspension, 311 "Download in progress")); 312 313 return new DownloadFileWithErrors( 314 save_info.Pass(), 315 default_download_directory, 316 url, 317 referrer_url, 318 calculate_hash, 319 stream.Pass(), 320 bound_net_log, 321 psb.Pass(), 322 observer, 323 injected_errors_[url.spec()], 324 construction_callback_, 325 destruction_callback_); 326} 327 328bool 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 336void DownloadFileWithErrorsFactory::ClearErrors() { 337 injected_errors_.clear(); 338} 339 340TestFileErrorInjector::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 362TestFileErrorInjector::~TestFileErrorInjector() { 363} 364 365bool 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 376void TestFileErrorInjector::ClearErrors() { 377 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 378 injected_errors_.clear(); 379} 380 381bool 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 398size_t TestFileErrorInjector::CurrentFileCount() const { 399 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 400 return files_.size(); 401} 402 403size_t TestFileErrorInjector::TotalFileCount() const { 404 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 405 return found_files_.size(); 406} 407 408 409bool TestFileErrorInjector::HadFile(const GURL& url) const { 410 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 411 412 return (found_files_.find(url) != found_files_.end()); 413} 414 415void TestFileErrorInjector::ClearFoundFiles() { 416 found_files_.clear(); 417} 418 419void 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 427void TestFileErrorInjector::DestroyingDownloadFile(GURL url) { 428 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 429 DCHECK(files_.find(url) != files_.end()); 430 431 files_.erase(url); 432} 433 434void TestFileErrorInjector::RecordDownloadFileConstruction(const GURL& url) { 435 BrowserThread::PostTask( 436 BrowserThread::UI, 437 FROM_HERE, 438 base::Bind(&TestFileErrorInjector::DownloadFileCreated, this, url)); 439} 440 441void TestFileErrorInjector::RecordDownloadFileDestruction(const GURL& url) { 442 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, 443 base::Bind(&TestFileErrorInjector::DestroyingDownloadFile, this, url)); 444} 445 446// static 447scoped_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 460std::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